دمج وكيل Dialogflow في تطبيق React

نشرت: 2022-03-10
ملخص سريع ↬ عندما يتعلق الأمر ببناء مساعد محادثة محادثة يمكن استخدامه على مستوى المؤسسات الصغيرة أو المؤسسات ، فمن المرجح أن يكون Dialogflow أحد الخيارات الأولى التي ستظهر في قائمة البحث الخاصة بك - ولماذا لا؟ يوفر العديد من الميزات مثل القدرة على معالجة مدخلات الصوت والنص ، وتقديم استجابات ديناميكية باستخدام خطافات الويب المخصصة ، والاتصال بملايين الأجهزة التي تدعم Google باستخدام مساعد Google ، وغير ذلك الكثير. ولكن بصرف النظر عن وحدة التحكم الخاصة بها التي يتم توفيرها لتصميم الوكيل وإدارته ، كيف يمكننا إنشاء مساعد دردشة يمكن استخدامه في تطبيقات الويب المبنية لدينا أيضًا؟

Dialogflow هو نظام أساسي يبسط عملية إنشاء وتصميم مساعد محادثة محادثة معالجة اللغة الطبيعية والذي يمكنه معالجة إدخال الصوت أو النص عند استخدامه إما من وحدة تحكم Dialogflow أو من تطبيق ويب متكامل.

على الرغم من أنه تم شرح عامل Dialogflow المدمج بإيجاز في هذه المقالة ، فمن المتوقع أن يكون لديك فهم لـ Node.js و Dialogflow. إذا كنت تتعرف على Dialogflow لأول مرة ، فإن هذه المقالة تقدم شرحًا واضحًا لما هو Dialogflow ومفاهيمه.

هذه المقالة عبارة عن دليل حول كيفية إنشاء وكيل Dialogflow مع دعم صوتي ودردشة يمكن دمجه في تطبيق ويب بمساعدة تطبيق Express.js الخلفي كحلقة وصل بين تطبيق ويب React.js والوكيل على Dialogflow نفسه. بنهاية المقالة ، يجب أن تكون قادرًا على توصيل وكيل Dialogflow الخاص بك بتطبيق الويب المفضل لديك.

لتسهيل متابعة هذا الدليل ، يمكنك التخطي إلى أي جزء من البرنامج التعليمي يهمك أكثر أو متابعته بالترتيب التالي كما يظهر:

  • إعداد عامل تدفق الحوار
  • تكامل عامل تدفق الحوار
  • إعداد تطبيق Node Express
    • المصادقة مع Dialogflow
    • ما هي حسابات الخدمة؟
    • التعامل مع المدخلات الصوتية
  • الدمج في تطبيق ويب
    • إنشاء واجهة دردشة
    • تسجيل إدخال صوت المستخدم
  • خاتمة
  • مراجع

1. إعداد عامل تدفق الحوار

كما هو موضح في هذه المقالة ، يُطلق على مساعد الدردشة على Dialogflow الوكيل ويتألف من مكونات أصغر مثل النوايا والوفاء وقاعدة المعرفة وغير ذلك الكثير. يوفر Dialogflow وحدة تحكم للمستخدمين لإنشاء وتدريب وتصميم تدفق محادثة الوكيل. في حالة الاستخدام الخاصة بنا ، سنقوم باستعادة الوكيل الذي تم تصديره إلى مجلد ZIP بعد تدريبه ، وذلك باستخدام ميزة تصدير واستيراد الوكيل.

قبل إجراء الاستيراد ، نحتاج إلى إنشاء وكيل جديد يتم دمجه مع الوكيل على وشك استعادته. لإنشاء وكيل جديد من وحدة التحكم ، يلزم وجود اسم فريد وأيضًا مشروع على Google Cloud لربط الوكيل به. إذا لم يكن هناك مشروع حالي على Google Cloud للربط به ، فيمكن إنشاء مشروع جديد هنا.

تم إنشاء وكيل وتدريبه مسبقًا للتوصية بمنتجات النبيذ للمستخدم بناءً على ميزانيته. تم تصدير هذا الوكيل إلى ملف ZIP ؛ يمكنك تنزيل المجلد هنا واستعادته إلى وكيلنا الذي تم إنشاؤه حديثًا من علامة التبويب "تصدير واستيراد" الموجودة في صفحة إعدادات الوكيل.

استعادة وكيل تم تصديره مسبقًا من مجلد ZIP
استعادة وكيل تم تصديره مسبقًا من مجلد ZIP. (معاينة كبيرة)

تم تدريب الوكيل المستورد مسبقًا على التوصية بمنتج نبيذ للمستخدم بناءً على ميزانية المستخدم لشراء زجاجة نبيذ.

بالمرور عبر الوكيل المستورد ، سنرى أنه يحتوي على ثلاث نوايا تم إنشاؤها من صفحة النوايا. أحدهما عبارة عن نية احتياطية ، تُستخدم عندما لا يتعرف الوكيل على مدخلات المستخدم ، والآخر هو نية ترحيب تُستخدم عند بدء محادثة مع الوكيل ، ويتم استخدام النية الأخيرة للتوصية بنبيذ للمستخدم بناءً على معلمة المبلغ داخل الجملة. مصدر قلقنا هو نية get-wine-recommendation

تحتوي هذه النية على سياق إدخال واحد wine-recommendation تأتي من نية الترحيب الافتراضية لربط المحادثة بهذه النية.

"السياق هو نظام داخل وكيل يستخدم للتحكم في تدفق المحادثة من نية إلى أخرى."

يوجد أسفل السياقات عبارات التدريب ، وهي جمل تُستخدم لتدريب الوكيل على نوع العبارات التي يمكن توقعها من المستخدم. من خلال مجموعة كبيرة ومتنوعة من عبارات التدريب داخل النية ، يكون الوكيل قادرًا على التعرف على جملة المستخدم والنية التي تقع فيها.

تشير عبارات التدريب داخل نية get-wine-recommendation وكلائنا (كما هو موضح أدناه) إلى اختيار النبيذ وفئة السعر:

قائمة بعبارات التدريب المتاحة مع نية الحصول على النبيذ.
صفحة نية الحصول على النبيذ - توصية تعرض عبارات التدريب المتاحة. (معاينة كبيرة)

بالنظر إلى الصورة أعلاه ، يمكننا أن نرى عبارات التدريب المتاحة مدرجة في القائمة ، ويتم تمييز رقم العملة باللون الأصفر لكل منها. يُعرف هذا التمييز باسم التعليق التوضيحي على Dialogflow ويتم تلقائيًا استخراج أنواع البيانات المعروفة باسم الكيان من جملة المستخدم.

بعد مطابقة هذه النية في محادثة مع الوكيل ، سيتم تقديم طلب HTTP إلى خدمة خارجية للحصول على النبيذ الموصى به بناءً على السعر المستخرج كمعامل من جملة المستخدم ، من خلال استخدام webhook الممكّن الموجود داخل قسم الاستيفاء أسفل صفحة النية هذه.

يمكننا اختبار الوكيل باستخدام محاكي Dialogflow الموجود في القسم الأيمن من وحدة تحكم Dialogflow. للاختبار ، نبدأ المحادثة برسالة " مرحبًا " ونتابعها بكمية النبيذ المطلوبة. سيتم استدعاء webhook على الفور وسيظهر الوكيل استجابة غنية مماثلة لتلك الواردة أدناه.

اختبار webhook الوكيل المستورد.
اختبار خطاف الويب لاستيفاء الوكيل المستورد باستخدام محاكي الوكيل في وحدة التحكم. (معاينة كبيرة)

من الصورة أعلاه ، يمكننا رؤية عنوان URL الخاص بخطاف الويب الذي تم إنشاؤه باستخدام Ngrok واستجابة الوكيل على الجانب الأيمن والتي تعرض نبيذًا ضمن النطاق السعري البالغ 20 دولارًا الذي كتبه المستخدم.

في هذه المرحلة ، تم إعداد وكيل Dialogflow بالكامل. يمكننا الآن البدء في دمج هذا الوكيل في تطبيق ويب لتمكين المستخدمين الآخرين من الوصول إلى الوكيل والتفاعل معه دون الوصول إلى وحدة تحكم Dialogflow الخاصة بنا.

المزيد بعد القفز! أكمل القراءة أدناه ↓

دمج عامل تدفق الحوار

في حين أن هناك وسائل أخرى للاتصال بعامل Dialogflow مثل تقديم طلبات HTTP لنقاط نهاية REST الخاصة به ، فإن الطريقة الموصى بها للاتصال بـ Dialogflow هي من خلال استخدام مكتبة العميل الرسمية المتاحة بعدة لغات برمجة. بالنسبة إلى JavaScript ، تتوفر حزمة @ google-cloud / Dialflow للتثبيت من NPM.

داخليًا ، تستخدم حزمة @ google-cloud / الحوار gRPC لاتصالات الشبكة الخاصة بها وهذا يجعل الحزمة غير مدعومة داخل بيئة المتصفح إلا عند تصحيحها باستخدام حزمة الويب ، والطريقة الموصى بها لاستخدام هذه الحزمة هي من بيئة Node. يمكننا القيام بذلك عن طريق إعداد تطبيق Express.js للجهة الخلفية لاستخدام هذه الحزمة ثم تقديم البيانات إلى تطبيق الويب من خلال نقاط نهاية API الخاصة به وهذا ما سنفعله بعد ذلك.

إعداد تطبيق Node Express

لإعداد تطبيق سريع ، نقوم بإنشاء دليل مشروع جديد ثم الحصول على التبعيات المطلوبة باستخدام yarn من محطة سطر أوامر مفتوحة.

 # 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

مع تثبيت التبعيات المطلوبة ، يمكننا المضي قدمًا في إعداد خادم Express.js ضعيف جدًا يتعامل مع الاتصالات على منفذ محدد مع تمكين دعم CORS لتطبيق الويب.

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

عند التنفيذ ، يبدأ الرمز الموجود في المقتطف أعلاه خادم HTTP يستمع إلى الاتصالات على PORT Express.js المحدد. كما تم تمكين مشاركة الموارد عبر الأصل (CORS) في جميع الطلبات التي تستخدم حزمة cors كبرنامج وسيط سريع. في الوقت الحالي ، لا يستمع هذا الخادم إلا للاتصالات ، ولا يمكنه الاستجابة لطلب لأنه ليس لديه مسار تم إنشاؤه ، لذلك دعونا ننشئ هذا.

نحتاج الآن إلى إضافة مسارين جديدين: أحدهما لإرسال بيانات نصية والآخر لإرسال إدخال صوتي مسجل. سيقبل كلاهما طلب POST ويرسل البيانات الواردة في نص الطلب إلى وكيل 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

أعلاه أنشأنا مثيل جهاز توجيه منفصل لطريقتي POST التي تم إنشاؤها والتي لا تستجيب في الوقت الحالي إلا برمز حالة 200 واستجابة وهمية مشفرة. عندما ننتهي من المصادقة باستخدام Dialogflow ، يمكننا العودة لتنفيذ اتصال فعلي بـ Dialogflow ضمن نقاط النهاية هذه.

للخطوة الأخيرة في إعداد تطبيق الواجهة الخلفية لدينا ، نقوم بتركيب مثيل جهاز التوجيه الذي تم إنشاؤه مسبقًا والذي تم إنشاؤه في تطبيق Express باستخدام app.use والمسار الأساسي للمسار.

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

أعلاه ، أضفنا مسارًا أساسيًا إلى المسارين اثنين يمكننا اختبار أي منهما عبر طلب POST باستخدام cURL من سطر أوامر كما هو الحال أدناه مع نص طلب فارغ ؛

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

بعد إكمال الطلب أعلاه بنجاح ، يمكننا أن نتوقع رؤية استجابة تحتوي على بيانات كائن يتم طباعتها إلى وحدة التحكم.

لقد تركنا الآن إجراء اتصال فعلي بـ Dialogflow والذي يتضمن التعامل مع المصادقة وإرسال واستقبال البيانات من الوكيل على Dialogflow باستخدام @ google-cloud / حزمة الحوار.

المصادقة مع Dialogflow

يرتبط كل وكيل Dialogflow تم إنشاؤه بمشروع على Google Cloud. للاتصال خارجيًا بوكيل Dialogflow ، نقوم بالمصادقة مع المشروع على سحابة Google واستخدام Dialogflow كأحد موارد المشروع. من بين الطرق الست المتاحة للاتصال بمشروع على google-cloud ، يعد استخدام خيار حسابات الخدمة هو الأكثر ملاءمة عند الاتصال بخدمة معينة على google cloud من خلال مكتبة العميل الخاصة بها.

ملاحظة : بالنسبة للتطبيقات الجاهزة للإنتاج ، يوصى باستخدام مفاتيح API قصيرة العمر على مفاتيح حساب الخدمة لتقليل مخاطر وقوع مفتاح حساب الخدمة في الأيدي الخطأ.

ما هي حسابات الخدمة؟

حسابات الخدمة هي نوع خاص من الحسابات على Google Cloud ، تم إنشاؤها للتفاعل غير البشري ، غالبًا من خلال واجهات برمجة التطبيقات الخارجية. في تطبيقنا ، سيتم الوصول إلى حساب الخدمة من خلال مفتاح تم إنشاؤه بواسطة مكتبة عميل Dialogflow للمصادقة مع Google Cloud.

توفر وثائق السحابة الخاصة بإنشاء حسابات الخدمة وإدارتها دليلاً ممتازًا لإنشاء حساب خدمة. عند إنشاء حساب الخدمة ، يجب تعيين دور مسؤول Dialogflow API لحساب الخدمة الذي تم إنشاؤه كما هو موضح في الخطوة الأخيرة. يمنح هذا الدور حساب الخدمة تحكمًا إداريًا في عامل Dialogflow المرتبط.

لاستخدام حساب الخدمة ، نحتاج إلى إنشاء مفتاح حساب الخدمة. توضح الخطوات التالية أدناه كيفية إنشاء واحد بتنسيق JSON:

  1. انقر فوق حساب الخدمة الذي تم إنشاؤه حديثًا للانتقال إلى صفحة حساب الخدمة.
  2. قم بالتمرير إلى قسم المفاتيح وانقر على القائمة المنسدلة إضافة مفتاح وانقر على خيار إنشاء مفتاح جديد الذي يفتح نموذجًا.
  3. حدد تنسيق ملف JSON وانقر فوق الزر "إنشاء" في أسفل يمين النموذج.

ملاحظة: يوصى بالحفاظ على خصوصية مفتاح حساب الخدمة وعدم إلزامه بأي نظام تحكم في الإصدار لأنه يحتوي على بيانات حساسة للغاية حول مشروع على Google Cloud. يمكن القيام بذلك عن طريق إضافة الملف إلى ملف .gitignore .

من خلال إنشاء حساب الخدمة وإتاحة مفتاح حساب الخدمة داخل دليل مشروعنا ، يمكننا استخدام مكتبة عميل Dialogflow لإرسال البيانات واستلامها من وكيل 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;

يرسل المسار بالكامل أعلاه البيانات إلى وكيل Dialogflow ويتلقى استجابة من خلال الخطوات التالية.

  • أولا
    يقوم بالمصادقة باستخدام سحابة Google ثم يقوم بإنشاء جلسة مع Dialogflow باستخدام معرف المشروع الخاص بمشروع Google السحابي المرتبط بوكيل Dialogflow وأيضًا معرّف عشوائي لتحديد الجلسة التي تم إنشاؤها. في تطبيقنا ، نقوم بإنشاء معرف UUID في كل جلسة تم إنشاؤها باستخدام حزمة JavaScript UUID. هذا مفيد جدًا عند تسجيل أو تتبع جميع المحادثات التي تتم معالجتها بواسطة وكيل Dialogflow.
  • ثانيا
    نقوم بإنشاء بيانات كائن الطلب باتباع التنسيق المحدد في وثائق Dialogflow. يحتوي كائن الطلب هذا على الجلسة التي تم إنشاؤها وبيانات الرسالة التي تم الحصول عليها من نص الطلب الذي سيتم تمريره إلى وكيل Dialogflow.
  • ثالث
    باستخدام طريقة detectIntent من جلسة Dialogflow ، نرسل كائن الطلب بشكل غير متزامن وننتظر استجابة الوكيل باستخدام ES6 غير المتزامن / في انتظار بناء الجملة في كتلة try-catch إذا detectIntent طريقة DiscoverIntent استثناءً ، يمكننا التقاط الخطأ وإعادته ، بدلاً من ذلك من تعطل التطبيق بأكمله. يتم توفير عينة من كائن الاستجابة الذي تم إرجاعه من الوكيل في وثائق Dialogflow ويمكن فحصها لمعرفة كيفية استخراج البيانات من الكائن.

يمكننا الاستفادة من Postman لاختبار اتصال Dialogflow الذي تم تنفيذه أعلاه في مسار dialogflow-response . Postman عبارة عن منصة تعاون لتطوير API مع ميزات لاختبار واجهات برمجة التطبيقات المدمجة في مراحل التطوير أو الإنتاج.

ملاحظة: إذا لم يكن مثبتًا بالفعل ، فلن يكون تطبيق Postman لسطح المكتب ضروريًا لاختبار API. اعتبارًا من سبتمبر 2020 ، انتقل عميل الويب الخاص بـ Postman إلى الحالة العامة المتاحة (GA) ويمكن استخدامه مباشرة من المتصفح.

باستخدام Postman Web Client ، يمكننا إما إنشاء مساحة عمل جديدة أو استخدام مساحة موجودة لإنشاء طلب POST لنقطة نهاية API الخاصة بنا على https://localhost:5000/api/agent/text-input وإضافة البيانات بمفتاح message وقيمة " مرحبًا بكم " في معلمات الاستعلام.

عند النقر فوق الزر إرسال ، سيتم تقديم طلب POST إلى خادم Express قيد التشغيل - مع استجابة مماثلة لتلك الموضحة في الصورة أدناه:

اختبار نقطة نهاية API لإدخال النص باستخدام Postman.
اختبار نقطة نهاية API لإدخال النص باستخدام Postman. (معاينة كبيرة)

في الصورة أعلاه ، يمكننا رؤية بيانات الاستجابة المُحسَّنة من وكيل Dialogflow عبر خادم Express. يتم تنسيق البيانات التي تم إرجاعها وفقًا لتعريف نموذج الاستجابة الوارد في وثائق Dialogflow Webhook.

التعامل مع المدخلات الصوتية

بشكل افتراضي ، يتم تمكين جميع وكلاء Dialogflow لمعالجة كل من البيانات النصية والصوتية وإرجاع استجابة إما بتنسيق نصي أو صوتي. ومع ذلك ، يمكن أن يكون العمل مع إدخال الصوت أو بيانات الإخراج أكثر تعقيدًا قليلاً من البيانات النصية.

للتعامل مع المدخلات الصوتية ومعالجتها ، سنبدأ في تنفيذ نقطة النهاية /voice-input التي أنشأناها مسبقًا لتلقي الملفات الصوتية وإرسالها إلى Dialogflow مقابل رد من الوكيل:

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

في نظرة عامة عالية ، يتلقى مسار /voice-input أعلاه الإدخال الصوتي للمستخدم كملف يحتوي على الرسالة التي يتم التحدث بها إلى مساعد الدردشة ويرسلها إلى وكيل Dialogflow. لفهم هذه العملية بشكل أفضل ، يمكننا تقسيمها إلى الخطوات الأصغر التالية:

  • أولاً ، نقوم بإضافة واستخدام connect-busboy كبرنامج وسيط سريع لتحليل بيانات النموذج التي يتم إرسالها في الطلب من تطبيق الويب. بعد ذلك قمنا بالمصادقة مع Dialogflow باستخدام مفتاح الخدمة وإنشاء جلسة ، بنفس الطريقة التي فعلناها في المسار السابق.
    ثم باستخدام طريقة promisify من وحدة استخدام Node.js المضمنة ، نحصل ونحفظ وعدًا مكافئًا لطريقة خط أنابيب التدفق لاستخدامها لاحقًا لتدفق تيارات متعددة وأيضًا إجراء تنظيف بعد اكتمال التدفقات.
  • بعد ذلك ، نقوم بإنشاء كائن طلب يحتوي على جلسة مصادقة Dialogflow وتكوين لملف الصوت الذي سيتم إرساله إلى Dialogflow. يمكّن كائن تكوين الصوت المتداخل وكيل Dialogflow من إجراء تحويل الكلام إلى نص على الملف الصوتي المرسل.
  • بعد ذلك ، باستخدام الجلسة التي تم إنشاؤها وكائن الطلب ، نكتشف نية المستخدم من ملف الصوت باستخدام طريقة detectStreamingIntent التي تفتح دفقًا جديدًا للبيانات من وكيل Dialogflow إلى تطبيق الواجهة الخلفية. سوف ترسل البيانات مرة أخرى بتات صغيرة من خلال هذا الدفق وباستخدام " حدث " البيانات من التدفق المقروء نقوم بتخزين البيانات في متغير streamData لاستخدامها لاحقًا. بعد إغلاق الدفق ، يتم تشغيل الحدث " end " ونرسل الاستجابة من وكيل Dialogflow المخزن في متغير streamData إلى تطبيق الويب.
  • أخيرًا باستخدام حدث دفق الملف من connect-busboy ، نتلقى دفق الملف الصوتي المرسل في نص الطلب ونمرره أيضًا إلى الوعد المكافئ لـ Pipeline الذي أنشأناه سابقًا. تتمثل وظيفة هذا في توجيه دفق ملف الصوت القادم من الطلب إلى دفق Dialogflow ، فنحن نقوم بتوجيه دفق ملف الصوت إلى الدفق الذي تم فتحه بواسطة طريقة detectStreamingIntent أعلاه.

لاختبار وتأكيد أن الخطوات المذكورة أعلاه تعمل على النحو المبين ، يمكننا تقديم طلب اختبار يحتوي على ملف صوتي في نص الطلب إلى نقطة النهاية /voice-input باستخدام Postman.

اختبار نقطة نهاية API الإدخال الصوتي باستخدام Postman.
اختبار نقطة نهاية API إدخال الصوت باستخدام ساعي البريد مع ملف صوتي مسجل. (معاينة كبيرة)

تُظهر نتيجة ساعي البريد أعلاه الاستجابة التي تم الحصول عليها بعد إجراء طلب POST مع بيانات النموذج لرسالة ملاحظة صوتية مسجلة تقول " مرحبًا " مضمنة في نص الطلب.

في هذه المرحلة ، لدينا الآن تطبيق Express.js وظيفي يرسل البيانات ويستقبلها من Dialogflow ، وقد تم الانتهاء من جزأين من هذه المقالة. أين تبقى الآن مع دمج هذا العامل في تطبيق ويب عن طريق استهلاك واجهات برمجة التطبيقات التي تم إنشاؤها هنا من تطبيق Reactjs.

الدمج في تطبيق ويب

لاستهلاك واجهة برمجة تطبيقات REST التي تم بناؤها ، سنقوم بتوسيع تطبيق React.js الحالي الذي يحتوي بالفعل على صفحة رئيسية تعرض قائمة بالنبيذ الذي تم جلبه من واجهة برمجة التطبيقات ودعم مصممي الديكور باستخدام ملحق babel للديكور. سنقوم بإعادة تشكيلها قليلاً من خلال تقديم Mobx لإدارة الحالة وأيضًا ميزة جديدة للتوصية بنبيذ من مكون دردشة باستخدام نقاط نهاية REST API المضافة من تطبيق Express.js.

للبدء ، نبدأ في إدارة حالة التطبيق باستخدام MobX حيث نقوم بإنشاء متجر Mobx مع بعض القيم التي يمكن ملاحظتها وبعض الطرق كإجراءات.

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

أعلاه أنشأنا متجرًا لميزة مكون الدردشة داخل التطبيق يحتوي على القيم التالية:

  • isChatWindowOpen
    تتحكم القيمة المخزنة هنا في رؤية مكون الدردشة حيث تظهر رسائل Dialogflow.
  • isLoadingChatMessages
    يستخدم هذا لإظهار مؤشر التحميل عند تقديم طلب لجلب استجابة من وكيل Dialogflow.
  • agentMessages
    يخزن هذا المصفوفة جميع الاستجابات الواردة من الطلبات المقدمة للحصول على استجابة من وكيل Dialogflow. يتم عرض البيانات الموجودة في المصفوفة لاحقًا في المكون.
  • handleConversation
    تم تزيين هذه الطريقة كإجراء لإضافة البيانات إلى مصفوفة agentMessages . أولاً ، يضيف رسالة المستخدم التي تم تمريرها كوسيطة ثم يقدم طلبًا باستخدام Axios إلى تطبيق الواجهة الخلفية للحصول على استجابة من Dialogflow. بعد حل الطلب ، يقوم بإضافة الاستجابة من الطلب إلى مصفوفة agentMessages .

ملاحظة: في حالة عدم وجود دعم مصممي الديكور في أحد التطبيقات ، يوفر MobX makeObservable الذي يمكن استخدامه في مُنشئ فئة المتجر الهدف. انظر الى مثال هنا .

مع إعداد المتجر ، نحتاج إلى التفاف شجرة التطبيق بالكامل باستخدام مكون MobX Provider ذي الترتيب الأعلى بدءًا من المكون الجذر في ملف 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;

أعلاه ، نلتف مكون تطبيق الجذر مع موفر MobX ونمرر في المتجر الذي تم إنشاؤه مسبقًا كإحدى قيم الموفر. الآن يمكننا المتابعة للقراءة من المتجر داخل المكونات المتصلة بالمتجر.

إنشاء واجهة دردشة

لعرض الرسائل المرسلة أو المستلمة من طلبات واجهة برمجة التطبيقات ، نحتاج إلى مكون جديد مع واجهة دردشة تعرض الرسائل المدرجة. للقيام بذلك ، نقوم بإنشاء مكون جديد لعرض بعض الرسائل المشفرة أولاً ثم لاحقًا نعرض الرسائل في قائمة مرتبة.

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

يحتوي المكون أعلاه على ترميز HTML الأساسي المطلوب لتطبيق الدردشة. يحتوي على رأس يوضح اسم الوكيل ورمزًا لإغلاق نافذة الدردشة ، وفقاعة رسالة تحتوي على نص ثابت في علامة قائمة ، وأخيرًا حقل إدخال به معالج حدث onChange مرفق بحقل الإدخال لتخزين النص المكتوب فيه الحالة المحلية للمكون باستخدام حالة استخدام React.

معاينة مكون الدردشة برسالة مشفرة من وكيل الدردشة
معاينة مكون الدردشة برسالة مشفرة من وكيل الدردشة. (معاينة كبيرة)

من الصورة أعلاه ، يعمل مكون الدردشة كما ينبغي ، ويعرض نافذة دردشة على غرار تحتوي على رسالة دردشة واحدة وحقل الإدخال في الأسفل. ومع ذلك ، نريد أن تكون الرسالة المعروضة ردود فعلية تم الحصول عليها من طلب واجهة برمجة التطبيقات وليست نصًا مشفرًا.

نتقدم إلى الأمام لإعادة تشكيل مكون الدردشة ، هذه المرة الاتصال والاستفادة من القيم في متجر MobX داخل المكون.

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

من الأجزاء المميزة في الكود أعلاه ، يمكننا أن نرى أن مكون الدردشة بالكامل قد تم تعديله الآن لإجراء العمليات الجديدة التالية ؛

  • يمكنه الوصول إلى قيم متجر MobX بعد إدخال قيمة ApplicationStore . تم أيضًا جعل المكون مراقبًا لقيم المتجر هذه ، لذا فإنه يعيد عرضه عند تغيير إحدى القيم.
  • نبدأ المحادثة مع الوكيل فور فتح مكون الدردشة عن طريق استدعاء طريقة handleConversation داخل خطاف useEffect لتقديم طلب فور تقديم المكون.
  • نحن الآن نستخدم قيمة isLoadingMessages داخل رأس مكون الدردشة. عندما يكون هناك طلب للحصول على رد من الوكيل في حالة طيران ، قمنا بتعيين قيمة isLoadingMessages على " true " وتحديث الرأس إلى Zara يكتب ...
  • يتم تحديث مصفوفة agentMessages داخل المتجر إلى وكيل بواسطة MobX بعد تعيين قيمها. من هذا المكون ، نقوم بتحويل هذا الوكيل مرة أخرى إلى مصفوفة باستخدام الأداة المساعدة toJS من MobX وتخزين القيم في متغير داخل المكون. يتم تكرار هذه المصفوفة أيضًا لملء فقاعات الدردشة بالقيم الموجودة داخل المصفوفة باستخدام وظيفة الخريطة.

الآن باستخدام مكون الدردشة ، يمكننا كتابة جملة وانتظار الرد حتى يتم عرضه في الوكيل.

مكوِّن الدردشة يعرض قائمة بيانات تم إرجاعها من طلب HTTP إلى التطبيق السريع.
مكوِّن الدردشة يعرض قائمة بيانات تم إرجاعها من طلب HTTP إلى التطبيق السريع. (معاينة كبيرة)

تسجيل إدخال صوت المستخدم

بشكل افتراضي ، يمكن لجميع وكلاء Dialogflow قبول الإدخال الصوتي أو النصي بأي لغة محددة من المستخدم. ومع ذلك ، فإنه يتطلب بعض التعديلات من تطبيق ويب للوصول إلى ميكروفون المستخدم وتسجيل إدخال صوتي.

لتحقيق ذلك ، قمنا بتعديل متجر MobX لاستخدام HTML MediaStream Recording API لتسجيل صوت المستخدم ضمن طريقتين جديدتين في متجر 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}`)); }; };

عند النقر على أيقونة التسجيل من مكون الدردشة ، يتم استدعاء طريقة startAudioConversation في متجر isRecording أعلاه لتعيين الطريقة التي يمكن ملاحظتها على خاصية التسجيل على صواب ، لكي يوفر مكون الدردشة ملاحظات مرئية لإظهار أن التسجيل قيد التقدم.

باستخدام واجهة المتصفح ، يتم الوصول إلى كائن Media Device لطلب ميكروفون جهاز المستخدم. بعد منح الإذن لطلب getUserMedia ، يفي بوعده ببيانات MediaStream التي نمررها أيضًا إلى مُنشئ MediaRecorder لإنشاء مسجل باستخدام مسارات الوسائط في الدفق الذي يتم إرجاعه من ميكروفون جهاز المستخدم. نقوم بعد ذلك بتخزين مثيل مسجل الوسائط في خاصية recorder المتجر حيث سنصل إليه من خلال طريقة أخرى لاحقًا.

بعد ذلك ، نسمي التابع start على مثيل المُسجل ، وبعد انتهاء جلسة التسجيل ، يتم تشغيل وظيفة ondataavailable مع وسيطة حدث تحتوي على الدفق المسجل في Blob الذي recordedBits في خاصية مجموعة RegisterBits.

عند تسجيل الخروج من البيانات الموجودة في وسيطة الحدث التي تم تمريرها إلى حدث ondataavailable ، يمكننا رؤية Blob وخصائصه في وحدة تحكم المتصفح.

تعرض وحدة تحكم Browser Devtools كائن تخزين البيانات الثنائية الكبيرة (Blob) الذي تم تسجيل الخروج منه والذي تم إنشاؤه بواسطة مسجل الوسائط بعد انتهاء التسجيل.
تعرض وحدة تحكم Browser Devtools نقطة تسجيل الخروج التي تم إنشاؤها بواسطة Media Recorder بعد انتهاء التسجيل. (معاينة كبيرة)

الآن بعد أن أصبح بإمكاننا بدء دفق MediaRecorder ، نحتاج إلى أن نكون قادرين على إيقاف دفق MediaRecorder عندما ينتهي المستخدم من تسجيل مدخلاته الصوتية وإرسال ملف الصوت الذي تم إنشاؤه إلى تطبيق Express.js.

الطريقة الجديدة المضافة إلى المتجر أدناه توقف الدفق وتقدم طلب POST الذي يحتوي على إدخال الصوت المسجل.

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

خاتمة

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.

مراجع

  • 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