Friday, January 4, 2019

MongoDB - E11000 duplicate key error


Summary

In this post, I discuss the symptoms and workaround for an atomicity bug in MongoDB.  This error manifests itself when performing collection updates from multiple threads, with upsert:true, on a unique key.  The bug has been open since 2014 and still unresolved.  https://jira.mongodb.org/browse/SERVER-14322

Bug Set Up

/*jshint esversion: 6 */

'use strict';
'use esversion 6';

const MongoClient = require('mongodb').MongoClient;
const CONNECTION_URL = 'mongodb://admin:mongo@localhost:27017';
const DB_NAME = 'testDB';
const COLLECTION = 'testCollection';

let connection;
let promises = [];

function update(i, db,  query, value, upsrt) {
 return db.collection(COLLECTION).updateOne(query, value, {'upsert' : upsrt})
 .then((result) => {
  return new Promise((resolve, reject) => { resolve('i: ' + i + ' time: ' + new Date().getTime());});
 })
 .catch(err => {
  throw err;
 });
}

MongoClient.connect(CONNECTION_URL, {useNewUrlParser : true})
.then((result) => {
 connection = result;
 const db = connection.db(DB_NAME);
 
 for (let i=0; i<5 .then="" :="" addtoset="" db="" funds="" i="" id="" indices="" johndoe="" let="" p="" promise.all="" promises.push="" promises="" query="" results="" return="" set="" true="" value=""> {
 results.forEach((result) => {
  console.log('result - ' + result);
 });
})
.catch(err => {
 console.log(err); 
})
.finally(() => {
 console.log('closing db connection');
 connection.close();
});

Explanation

The Node.js code above creates an array of promises, each of which attempts to perform a db update with upsert equal to 'true'.  The promises sent thru Promise.all to execute them all as a batch.

Results

The duplicate key error is generated, as expected.
{ MongoError: E11000 duplicate key error collection: testDB.testCollection index: _id_ dup key: { : "johnDoe" }
    at Function.create (/nas/archive/dev/workspace/blackjack/node_modules/mongodb-core/lib/error.js:43:12)
    at toError (/nas/archive/dev/workspace/blackjack/node_modules/mongodb/lib/utils.js:149:22)
    at coll.s.topology.update (/nas/archive/dev/workspace/blackjack/node_modules/mongodb/lib/operations/collection_ops.js:1399:39)
    at /nas/archive/dev/workspace/blackjack/node_modules/mongodb-core/lib/connection/pool.js:532:18
    at process.internalTickCallback (internal/process/next_tick.js:70:11)
  driver: true,
  name: 'MongoError',
  index: 0,
  code: 11000,
  errmsg:
   'E11000 duplicate key error collection: testDB.testCollection index: _id_ dup key: { : "johnDoe" }',
  [Symbol(mongoErrorContextSymbol)]: {} }
closing db connection
Below is the output of a read of this collection.  One update (#4 index) completed before the error was thrown and halted the execution.
> db.testCollection.find({});
{ "_id" : "johnDoe", "funds" : 400, "indices" : [ 4 ] }

Workaround

function updateWithRetry(i, db,  query, value, upsrt) {
 return db.collection(COLLECTION).updateOne(query, value, {'upsert' : upsrt})
 .then((result) => {
  return new Promise((resolve, reject) => { resolve('i: ' + i + ' time: ' + new Date().getTime());});
 })
 .catch(err => {
  if (err.code === 11000){
   console.log('i: ' + i + ' 11000 error');
   return updateWithRetry(i, db, query, value, false);
  }
  else {
   throw err;
  }
 });
}

Explanation

In this revised update block, I added a check for the 11000 error code.  If that's, in fact, the source of the error, I retry the update with a recursive call.

Results

i: 0 11000 error
i: 2 11000 error
i: 3 11000 error
i: 4 11000 error
result - i: 0 time: 1546649403149
result - i: 1 time: 1546649403146
result - i: 2 time: 1546649403149
result - i: 3 time: 1546649403149
result - i: 4 time: 1546649403149
closing db connection
> db.testCollection.find({});
{ "_id" : "johnDoe", "funds" : 400, "indices" : [ 1, 0, 2, 3, 4 ] }

Source


Copyright ©1993-2024 Joey E Whelan, All rights reserved.

Sunday, October 14, 2018

Thoughts on SOWs

Summary

This post is a departure from the normal technical content of this blog.  I'm going to share my thoughts on the topic of Statement of Work (SOW) development in the domain of IT services.  The content of this post is strictly my opinions as I'm not an attorney nor have any legal training whatsoever.  Those opinions are based on ~20 years in Professional Services and active in the writing and implementing of SOWs for technology services.  In many cases, the lessons described here are written in blood/tears from mistakes I and others have made in the past.  

SOW Components

A SOW is a contract, pure and simple.  It's the sort of contract that is the standard for describing services in the technology space.  Below are the typical sections in a SOW.

Header with Parties

This is generally a boilerplate section with a definition of the parties involved in the SOW.  There are typically two:  
  • Service Provider (SP):  This is the organization delivering the services.
  • Customer:  This is the recipient of those professional services.
Lines can be blurred in some instances.  Example:  A SP could be both a provider and recipient in an engagement if they are subcontracting some or all of the services being delivered to a Customer under a SOW.

Scope

The 'Scope' is (or should be, more on that later) the main content of the SOW.  This is where the tasks and deliverables of the included services are described.  This area is the domain of SP SOW author and typically gets the most attention from both the SP and Customer.  Rightfully so, as this area dictates what the Customer will actually receive for their money and what the SP is beholden to deliver.

Terms, Conditions, and Assumptions

Typically a section of the SOW is carved out for boilerplate legal language and/or clarifications to what is/is not included in the SOW scope.  This is usually a list of bullets.    The SOW author may put some content in this area, but this is usually the domain of both sides' Legal/Contracting organizations.

Pricing

This section covers just what you'd expect - the cost of the services described in the Scope.  The structure of the content here is dependent on the SOW design pattern being employed.  Those models are described next.

SOW Design Patterns

In my opinion, there are only two SOW models:  Fixed-bid, Time and Materials.  I'll discuss another model later that I consider an anti-pattern.

Fixed-Bid

This model can be employed when both sides (SP, Customer) clearly understand the services to be delivered.  This type of SOW is harder for the author to construct as more work has to be put into describing the scope.  It's also typically more difficult for the SP to execute profitably as the cost in fixed-bid engagements is just that - fixed.  Costs remain static if the corresponding scope also remains static.  The SP charges exactly what the Pricing section dictates - whether it took 1 or 1 million hours to deliver the tasks + deliverables of the Scope.  

All that being said, this is the SOW model that most Customers typically feel most comfortable with.  They have a budget for a project and want an assurance that they'll get the project completed in that budget.  Fixed-bid can provide that assurance - if requirements remain fixed.

Pricing for this model is best structured around billing milestones tied to a tangible activity or deliverable.  Each milestone represents a percentage of the total services cost of the SOW.  Milestones must be well-bounded as to what 'done' means for that milestone as billing is triggered when 'done' is achieved.  

Example milestone:  Acceptance of Design Document.  The 'Design Document' and the concept of 'Acceptance' must both be rigorously described in the Scope section of the SOW such that both parties agree to what completion of that milestone entails.

Time and Materials (T&M)

This model is typically employed when it is not possible to clearly define the scope of the given project.  That can be for a variety of reasons:  scope simply isn't known at the time of SOW development, requirements are fluid, or - the Customer simply wants access to resources with certain skill sets, i.e., staff augmentation, without specific deliverables.    This is in contrast to a project-based engagement.

As the name suggests, the Customer pays for exactly the amount of services delivered by the SP - typically, metered by the hour.  The SOW will state the number of hours included in the price and an hourly rate.  A good T&M SOW practice is to include a 'circuit-breaker' clause that states the Customer will be notified when the expended hours have reached some critical mass.  Example:  20% remaining.  The Customer can then decide whether they want to increase funding to extend the SOW.  In any case, the services end when the funded hours are expended regardless of the state of the project.

A really bad practice in T&M is to state that 'deliverables' are included (bad from the SP perspective, but probably considered fabulous from the Customer side).  Those two concepts are antithetical.  A 'deliverable' implies something that's guaranteed to be produced under the SOW.  The duration of a T&M SOW is by definition limited by the funded hours.  Scenario:  A 'deliverable' is incomplete but the funded hours have all been expended.  Now what?  

An anti-pattern variant of T&M is 'Capped T&M'.  This non-sensical model is an attempt at a hybrid of T&M and Fixed-bid:  the hours are capped at a level but the Customer only pays for the actual amount expended.  The real mess comes when the SOW author includes deliverables.  The Customer gets the fabulous deal of guaranteed 'deliverables' but only pays for actual hours expended up to a fixed maximum.  The SP gets the short end of that stick.  They didn't understand their scope well enough for a fixed-bid SOW but now have to deliver at what is, in essence, a fixed cost.  Net, this is an imbalanced situation.

Summary graphic below.


SOW Authors

I'm going to speak to this from the SP perspective as they are typically the party that generates the SOW.  It's their services and the SOW represents their quote for those services.  There are occasions when the Customer will generate the first draft of a SOW but those seem to be less common in my experience.

On the SP side, SOW development is almost always a team effort.  There is a main author of the scope content and then various other parties that provide review and/or auxiliary content.  Those other parties are invariably Contracting/Sourcing, Legal and the management of the Services arm that will be responsible to implement the tasks/deliverables of the SOW.

As far as that 'main author' - who should that be?  In my experience, the best results come from individuals that have real-world, hands-on experience in the technology.  This is almost always people that were/are in the Professional Services practice or have independently taken an active role in keeping themselves technically relevant.  It's these subject matter experts (SMEs) that are best suited to follow the sale from the beginning with the SP Sales team.  They hear the Customer explain the requirements first hand and understand any constraints that the Customer has expressed during the sales cycle.

I've seen a number of SPs that do not utilize SMEs for SOW development.  Instead, they'll use weak/generic scope statements or create a separate SOW group altogether that is detached from the Sales process.  Below is a listing of the issues I've seen over and over again with this model:
  • Authors that have no historical context on the engagement.  They weren't involved in the sales cycle so they didn't hear all the conversations with the Customer.  They come in cold to the sale.  As such, it's almost impossible for them to write a coherent fixed-bid SOW.  
  • Authors that have little to no technical or operational background.  They simply can't go to the level of detail necessary to capture the scope because they don't have a firm understanding of those details.
  • Authors that have too much technical background (and no sales background) and produce SOWs that go off the deep-end in technical content.  These SOWs wind up looking like technical design documents instead of a contract that a business leader is going to have to decipher and agree with prior to signing.
In most cases where the SP doesn't use their SE for SOW development, that SP is only comfortable with T&M engagements.  That's for good reason as their risks skyrocket in fixed-bid SOW's developed on incomplete information.

SOW Do's/Don't

  • #1 - Create a balanced first draft of the SOW and strive to keep it that way.  By 'balanced', I mean fair to both sides: Customer and SP.  Imbalanced SOWs just lead to protracted negotiations and in some cases - no sale.
  • Do write detailed scope content.
  • Don't write scope content that resembles a technical design document.
  • Don't put deliverables in T&M SOWs.
  • Don't put labor hour breakouts in Fixed-bid SOWs.  That's an unnecessary artifact that potentially leads to confusion and protracted negotiations.  By definition, the fixed-bid engagement will be delivered at a static cost.  The hours to do so are irrelevant.  Labor breakouts in T&M are fine and expected.
  • For Fixed-bid SOWs, do tie billing milestones to tangible results.
  • Avoid putting fixed delivery dates in any SOW.  Sometimes this cannot be avoided due to the Customer's requirements.  In those cases, something akin to a formal project plan is going to have to be developed prior.  That means the Services arm will have to be pulled in for a detailed analysis of the requirements and project plan development.  In most cases, that will be a non-billable exercise as it's prior to the SOW being executed.
  • Don't attempt to write an exclusion for every item you can think of that's out of scope.  By definition, anything that's not explicitly listed as in-scope is out of scope.  The universe of 'out of scope' is infinite.  You can't hope to capture infinity.
  • In the same vein, if the SOW has more content around exclusions than it does around scope definition - that SOW is likely of poor quality.  This is a sign that the author does not understand the engagement or technology. 

Copyright ©1993-2024 Joey E Whelan, All rights reserved.


Thursday, May 24, 2018

Google Maps API


Summary

In this post I'll show a very simple use-case of the Maps API.  I'll display a map with markers on a simple web page.

Code

Below is a simple javascript array of markers to be added to the map.  This array is stored in a file named markers.js.

'use strict';
'use esversion 6';
const sites = [ 
  { lat: 39.016683491757995, lng: -106.31219892539934, label: 'Site 1' },
  { lat: 39.01841939481699, lng: -106.31966973052391, label: 'Site 2' },
  { lat: 38.816564618651974, lng: -106.243267861874, label: 'Site 3' },
  { lat: 38.970910463727286, lng: -106.40428097049143, label: 'Site 4' }
];

Simple HTML + javascript code to display the map and markers below.

<!DOCTYPE html>
<html>
<head>
 <meta charset="UTF-8">
    <style>
      #map {
        height: 800px;
        width: 100%;
       }
    </style>
 <title>Fabulous Sites</title>
 <script type="text/javascript" src="markers.js"></script>
</head>
<body>
 <div id="map"></div>
 <script>
  function markMap() {
         const center = {lat: 38.8, lng: -106.24};
         const map = new google.maps.Map(document.getElementById('map'), {
           zoom: 10,
           center: center
         });
         for (let i=0; i < sites.length; i++)
          var marker = new google.maps.Marker({
            position: {lat: sites[i].lat, lng: sites[i].lng},
            label: sites[i].label,
            map: map
          }); 
  }
 </script>
 <script async defer
     src="https://maps.googleapis.com/maps/api/js?key=yourkey&callback=markMap">
    </script>
</body>
</html>

Results


Copyright ©1993-2024 Joey E Whelan, All rights reserved.

Google Sentiment Analytics


Summary

In this post, I'll demonstrate use of a Google Natural Language API - Sentiment.  The scenario will be a caller's interaction with a contact center agent is recorded and then sent through Google Speech to Text followed by Sentiment analysis.

Implementation

The diagram below depicts the overall implementation.  Calls enters an ACD/recording platform that has API access in and out.  Recordings are then sent into the Google API's for processing.



Below is a detailed flow of the interactions between the ACD platform and the Google API's.



Code Snippet

App Server

Simple Node.js server below representing the App Server component.
app.post(properties.path, jsonParser, (request, response) => {
 //send a response back to ACD immediately to release script-side resources
 response.status(200).end();
 
 const contactId = request.body.contactId;
 const fileName = request.body.fileName;
 let audio;
 
 logger.info(`contactId: ${contactId} webserver - fileName:${fileName}`);
 admin.get(contactId, fileName) //Fetch the audio file (Base64-encoded) from ACD
 .then((json) => {
  audio = json.file;
  return sentiment.process(contactId, audio);  //Get transcript and sentiment of audio bytes
 })
 .then((json) => { //Upload the audio, transcript, and sentiment to Google Cloud Storage
  return storage.upload(contactId, Buffer.from(audio, 'base64'), json.transcript, JSON.stringify(json.sentiment));
 })
 .then(() => {
  admin.remove(contactId, fileName); //Delete the audio file from ACD
 })
 .catch((err) => {
  logger.error(`contactId:${contactId} webserver - ${err}`);
 });
});

app.listen(properties.listenPort);
logger.info(`webserver - started on port ${properties.listenPort}`);


Source: https://github.com/joeywhelan/sentiment

Copyright ©1993-2024 Joey E Whelan, All rights reserved.

Sunday, April 15, 2018

Voice Interactions on AWS Lex + Google Dialogflow


Summary

In this post I'll discuss the audio capabilities of the bot frameworks in AWS and Google.  They have different approaches currently, though I think that's changing.  AWS Lex is fully-capable processing voice/audio in a single API call.  Google Dialogflow has a separation of concerns currently.  It takes three API calls to process a voice input and provide a voice response.  Interestingly enough, execution time on both platforms is roughly the same.

Voice Interaction Flow - AWS Lex

Diagram below of what things look like on Lex to process a voice interaction.  It's really simple.  A single API call (PostContent) can take audio as input and provide an audio bot response.  Lex is burying the speech-to-text and text-to-speech details such that the developer doesn't have to deal with it.  It's nice.


Code Snippet - AWS Lex

Simple function for submitting audio in and receiving audio out below.  The PostContent API call can process text or audio.

 send(userId, request) {
  let params = {
          botAlias: '$LATEST',
    botName: BOT_NAME,
    userId: userId,
    inputStream: request
  };
  
  switch (typeof request) {
   case 'string':
    params.contentType = 'text/plain; charset=utf-8';
    params.accept = 'text/plain; charset=utf-8';
    break;   
   case 'object':
    params.contentType = 'audio/x-l16; sample-rate=16000';
    params.accept = 'audio/mpeg';
    break;
  }
  return new Promise((resolve, reject) => {
   this.runtime.postContent(params, (err, data) => {
    if (err) {
     reject(err);
    }
    else if (data) {
     let response = {'text' : data.message};
     switch (typeof request) {
      case 'string':
       response.audio = '';
       break;
      case 'object':
       response.audio = Buffer.from(data.audioStream).toString('base64');
       break;
     }
     resolve(response);
    }
   });
  });
 }

Voice Interaction Flow - Google Dialogflow

Diagram of what the current state of affairs look like with Dialogflow and voice processing.  Each function (speech-to-text, bot, text-to-speech) require separate API calls.  At least that's the way it is in the V1 Dialogflow API.  From what I can tell in V2 (beta), it will allow for audio inputs.


Code Snippet - Google Dialogflow

Coding this up is more complicated than Lex, but nothing cosmic.  I wrote some wrapper functions around Javascript Fetch commands and then cascaded them via Promises as you see below.
 send(request) {
  return new Promise((resolve, reject) => {
   switch (typeof request) {
    case 'string':
     this._sendText(request)
     .then(text => {
      let response = {};
      response.text = text;
      response.audio = '';
      resolve(response);
     })
     .catch(err => { 
      console.error(err.message);
      reject(err);
     });  
     break;
    case 'object':
     let response = {};
     this._stt(request)
     .then((text) => {
      return this._sendText(text);
     })
     .then((text) => {
      response.text = text;
      return this._tts(text);
     })
     .then((audio) => {
      response.audio = audio;
      resolve(response);
     })
     .catch(err => { 
      console.error(err.message);
      reject(err);
     });  
   }
  });
 }

Results

I didn't expect this, but both platforms performed fairly equally even though multiple calls are necessary on Dialogflow.  For my simple bot example, I saw ~ 2 second execution times for audio in/out from both Lex and Dialogflow.  

Copyright ©1993-2024 Joey E Whelan, All rights reserved.

Saturday, April 7, 2018

Dialogflow & InContact Chat Integration


Summary

In this post I'll discuss how to integrate a chat session that starts with a bot in Google Dialogflow.  The user isn't able to complete the transaction with the bot and then requests a human agent for assistance.  The application then connects the user with an agent on InContact's cloud platform.  The bot and web interfaces I built here are crude/non-production quality.  The emphasis here is on API usage and integration thereof.

This the third post of three discussing chat with InContact and Dialogflow.


Architecture

Below is a diagram the overall architecture for the scenario discussed above.


Application Architecture

The application layer is a simple HTML page with the interface driven by a single Javascript file - chat.js.  I built wrapper classes for the Dialogflow and InContact REST API's:  dflow.js and incontactchat.js respectively.  The chat.js code invokes API calls via those classes.





Application Flow

The diagram below depicts the steps in this example scenario.  




Steps 5, 6 Screen-shots



Copyright ©1993-2024 Joey E Whelan, All rights reserved.

Tuesday, April 3, 2018

Google Dialogflow - Input Validation


Summary

This post concerns the task of validating user-entered input for a Google Dialogflow-driven chat agent.  My particular scenario is a quite simple/crude transactional flow, but I found input validation (slots) to be particularly cumbersome in Dialogflow.  Based on what I've seen in various forums, I'm not alone in that opinion.  Below are my thoughts on one way to handle input validation in Dialogflow.


Architecture

Below is high-level depiction of the Dialogflow architecture I utilized for my simple agent.  This particular agent is a repeat of something I did with AWS Lex (explanation here).  It's a firewood ordering agent.  The bot prompts for various items (number of cords, delivery address, etc) necessary to fulfill an order for firewood.  Really simple.



Below is my interpretation of the agent bot model in Dialogflow.

Validation Steps

For this simple, transactional agent I had various input items (slots) that needed to be provided by the end-user.  To validate those slots, I used two intents per item.  One intent was the main one that gathers the user's input.  That intent uses an input context to restrict access per the transactional flow.  The input to the intent is then be sent to a Google Cloud Function (GCF) for validation.  If it's valid, then a prompt is sent back to user for the next input slot.  If it's invalid, the GCF function triggers a follow-up intent to requery for that particular input item.  The user is trapped in that loop until they provide valid input.

Below is a diagram of the overall validation flow.


Below are screenshots of the Intent and requery-Intent for the 'number of cords' input item.  That item must be an integer between 1 and 3 for this simple scenario.



Code

Below is a depiction of the overall app architecture I used here.  All of the input validation is happening in a node.js function on GCF.


Validation function (firewoodWebhook.js)

The meaty parts of that function below:
function validate(data) { 
 console.log('validate: data.intentName - ' + data.metadata.intentName);
 switch (data.metadata.intentName) {
  case '3.0_getNumberCords':
   const cords = data.parameters.numberCords;
   if (cords && cords > 0 && cords < 4) {
    return new Promise((resolve, reject) => {
     const msg = 'We deliver within the 80863 zip code.  What is your street address?';
     const output = JSON.stringify({"speech": msg, "displayText": msg});
     resolve(output);
    });
   }
   else {
    return new Promise((resolve, reject) => {
     const output = JSON.stringify ({"followupEvent" : {"name":"requerynumbercords", "data":{}}});
     resolve(output);
    });
   }
   break;
  case '4.0_getStreet':
   const street = data.parameters.deliveryStreet;
   if (street) {
    return callStreetApi(street);
   }
   else {
    return new Promise((resolve, reject) => {
     const output = JSON.stringify ({"followupEvent" : {"name":"requerystreet", "data":{}}});
     resolve(output);
    });
   }
   break;
  case '5.0_getDeliveryTime':
   const dt = new Date(Date.parse(data.parameters.deliveryTime));
   const now = new Date();
   const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate()+1);
   const monthFromNow = new Date(now.getFullYear(), now.getMonth()+1, now.getDate());
   if (dt && dt.getUTCHours() >= 9 && dt.getUTCHours() <= 17 && dt >= tomorrow && dt <= monthFromNow) {
    return new Promise((resolve, reject) => {
     const contexts = data.contexts;
     let context = {};
     for (let i=0; i < contexts.length; i++){
      if (contexts[i].name === 'ordercontext') {
       context = contexts[i];
       break;
      }
     }
     const price = '$' + PRICE_PER_CORD[context.parameters.firewoodType] * context.parameters.numberCords;
     const msg = 'Thanks, your order for ' + context.parameters.numberCords + ' cords of ' + context.parameters.firewoodType + ' firewood ' + 
        'has been placed and will be delivered to ' + context.parameters.deliveryStreet + ' at ' + context.parameters.deliveryTime + '.  ' + 
        'We will need to collect a payment of ' + price + ' upon arrival.';
     const output = JSON.stringify({"speech": msg, "displayText": msg});
     resolve(output);
    });
   }
   else {
    return new Promise((resolve, reject) => {
     const output = JSON.stringify ({"followupEvent" : {"name":"requerydeliverytime", "data":{}}});
     resolve(output);   
    });
   }
   break;
  default:  //should never get here
   return new Promise((resolve, reject) => {
    const output = JSON.stringify ({"followupEvent" : {"name":"requestagent", "data":{}}});
    resolve(output);  
   });
 }
}
Focusing only on the number of cords validation -
Lines 6-11:  Check if the user input is between 1 and 3 cords.  If so, return a Promise object with the next prompt for input.
Lines 13-17:  Input is invalid.  Return a Promise object with a followupEvent to trigger the requery intent for this input item.

Client-side.  Dialogflow wrapper (dflow.js)

Meaty section of that below.  This is the 'send' function that submits user-input to Dialogflow for analysis and response.
 send(text) {
  const body = {'contexts': this.contexts,
      'query': text,
      'lang': 'en',
      'sessionId': this.sessionId
  };
  
  return fetch(this.url, {
   method: 'POST',
   body: JSON.stringify(body),
   headers: {'Content-Type' : 'application/json','Authorization' : 'Bearer ' + this.token},
   cache: 'no-store',
   mode: 'cors'
  })
  .then(response => response.json())
  .then(data => {
   console.log(data);
   if (data.status && data.status.code == 200) {
    this.contexts = data.result.contexts;
    return data.result.fulfillment.speech;
   }
   else {
    throw data.status.errorDetails;
   }
  })
  .catch(err => { 
   console.error(err);
   return 'We are experiencing technical difficulties.  Please contact an agent.';
  }) 
 }

Lines 8-29:  Main code here consists of a REST API call to Dialogflow with the user input.  If it's valid, return a Promise object with the next prompt.  Otherwise, send back a Promise with the error message.

Client-side.  User interface.

    function Chat(mode) {
        var _mode = mode;
     var _self = this;
        var _firstName;
        var _lastName;
        var _dflow; 
   
        this.start = function(firstName, lastName) {
            _firstName = firstName;
            _lastName = lastName;
            if (!_firstName || !_lastName) {
                alert('Please enter a first and last name');
                return;
            }
            
            _dflow = new DFlow("yourid");
            hide(getId('start'));
            show(getId('started'));
            getId('sendButton').disabled = false;
            getId('phrase').focus();
        };

        this.leave = function() {
         switch (_mode) {
          case 'dflow':       
           break;
         }
         getId('chat').innerHTML = '';
         show(getId('start'));
            hide(getId('started'));
            getId('firstName').focus();
        };
                       
        this.send = function() {
            var phrase = getId('phrase');
            var text = phrase.value.trim();
            phrase.value = '';

            if (text && text.length > 0) {
             var fromUser = _firstName + _lastName + ':'; 
             displayText(fromUser, text);
            
             switch (_mode) {
              case 'dflow':
               _dflow.send(text).then(resp => displayText('Bot:', resp));
               break;
             }
            }
        };         
Line 16:  Instantiate the Dialogflow wrapper object with your API token.
Line 45:  Call the 'send' function of the wrapper object and then display the returned text of the Promise.

Source Code


Copyright ©1993-2024 Joey E Whelan, All rights reserved.