├── README.md ├── .gitignore └── index.js /README.md: -------------------------------------------------------------------------------- 1 | # dialogflow-customerservice-agent 2 | ## Customer Service Agent for A Cleaning Service 3 | 4 | #### About 5 | This is a Dialogflow Customer Service Agent for a Cleaning Company. The chat bot uses context of conversation to trigger user intent. once user intent is correctly assessed an appropriate response is formed using the collected context. Some intents trigger a webhook to fetch, save or update data. 6 | 7 | 8 | #### Use 9 | To use this you will need the agent file and to upload it to GCS and publish a version 10 | 11 | latest agent: 2024_05_21 12 | 13 | #### Roadmap 14 | ##### Dialogflow integration 15 | Google Calendar 16 | DynamoDB 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // See https://github.com/dialogflow/dialogflow-fulfillment-nodejs 2 | // for Dialogflow fulfillment library docs, samples, and to report issues 3 | 'use strict'; 4 | 5 | const functions = require('firebase-functions'); 6 | const {WebhookClient} = require('dialogflow-fulfillment'); 7 | const {Card, Suggestion} = require('dialogflow-fulfillment'); 8 | 9 | process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements 10 | 11 | exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => { 12 | const agent = new WebhookClient({ request, response }); 13 | console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers)); 14 | console.log('Dialogflow Request body: ' + JSON.stringify(request.body)); 15 | console.log('[Firebase Fullfillment] Agent Parameters' + agent.parameters); 16 | 17 | 18 | //service 19 | const service = new Object ({ 20 | id: "string", 21 | typeofservice: "string", 22 | construct: "string", 23 | description:"string", 24 | numrooms: "string", 25 | numbaths: "string", 26 | sqft: "string", 27 | cleanfactor: "string" 28 | 29 | }); 30 | 31 | // user 32 | const user = new Object({ 33 | userid: "string", 34 | name: "string", 35 | surname: "string", 36 | email: "string", 37 | phone:"string", 38 | address: "string", 39 | service: "string", 40 | usermenuselection: "string", 41 | marketingtype: "string" 42 | 43 | }); 44 | 45 | // estimate 46 | const estimate = new Object({ 47 | costid: "string", 48 | svctotal: "string", 49 | extrasvctotal: "string", 50 | approved: "Boolean", 51 | userid: "string" 52 | }); 53 | 54 | // appointment 55 | const appointment = new Object({ 56 | date: "string", 57 | time: "string", 58 | location: "Boolean", 59 | userid: "boolean" 60 | }); 61 | 62 | const menu = ["schedule a cleaning", "list services", "get an estimate" ]; 63 | const services = ['Sparkle-izing is my standard service which includes me dusting, sweeping, mopping, scrubbing and polishing your place till its spotless', 'Extra Services would be stuff like Cleaning the Fridge or Oven', 'Professional Services such as shampooing rugs or cleaning your favorite couch' ]; 64 | //const descriptions = ['4 Bedroom, 4 Bath', '3 Bedroom, 3 Bath', '2 Bedroom, 2 Bath']; 65 | let marketingtype, usermenuselection, typeofservice, construct, description, numrooms, numbaths, sqft, cleanfactor; 66 | 67 | //Greeting (Main Menu) 68 | //Service Information 69 | //User Information 70 | //Estimate Creator 71 | //Appointment Scheduler 72 | // Take Payment 73 | 74 | 75 | function welcome(agent) { 76 | console.log('[Default Welcome Intent] agent parameters: ' + agent.parameters); 77 | //console.log('[Default Welcome Intent] agent.paramters.marketingtype ' + agent.parameters.marketingtype); 78 | 79 | 80 | 81 | // if (agent.parameters.typeofservice) { 82 | // typeofservice = agent.parameters.typeofservice; 83 | // service.typeofservice = typeofservice; 84 | // } 85 | 86 | // if (agent.parameters.construct) { 87 | // construct = agent.parameters.construct; 88 | // service.construct = construct; 89 | // } 90 | // // Marketing Analytics 91 | // // USER 92 | // if (agent.parameters.usermenuselection) { 93 | // usermenuselection = agent.parameters.usermenuselection; 94 | // user.usermenuselection = usermenuselection; 95 | // } 96 | 97 | // if (agent.parameters.marketingtype) { 98 | // marketingtype = agent.parameters.marketingtype; 99 | // user.marketingtype = marketingtype; 100 | // } 101 | 102 | //check what converted them for marketing analytics 103 | 104 | 105 | // if (usermenuselection && marketingtype) { 106 | // //they didn't mention what they responded to, and we provide them a lead in to their selected action and type of service 107 | // console.log('[Default Welcome Intent]- ' + ' typeofservice: ' + typeofservice + ' usermenuselection: ' + usermenuselection); 108 | // agent.add(`Thanks for checking out our ${marketingtype}!. Of course, I can help you with an ${usermenuselection}!`); 109 | // } else { 110 | // agent.add(`Thanks for texting. Do you need an estimate or would you like to schedule a cleaning?`); 111 | 112 | // } 113 | 114 | /// back to getting a lot of stuff 115 | // if (typeofservice && usermenuselection && marketingtype && construct) { 116 | // // custom message showing thanks for responding to the ad and providing them a lead in to their selected action 117 | // console.log('[Default Welcome Intent]- ' + ' typeofservice: ' + typeofservice + 'construct: ' + construct + ' usermenuselection: ' + usermenuselection + ' marketingtype: ' + marketingtype); 118 | // agent.add(`Thanks for checking out our ${marketingtype}!. Of course we can help you ${usermenuselection} for ${typeofservice} you ${construct}!`); 119 | 120 | // } else if (typeofservice && usermenuselection && marketingtype && !construct) { 121 | // // they didn't mention what they responded to, and we provide them a lead in to their selected action and type of service 122 | // console.log('[Default Welcome Intent]- ' + ' typeofservice: ' + typeofservice + ' usermenuselection: ' + usermenuselection + ' marketingtype: ' + marketingtype); 123 | // agent.add(`Glad you liked my ${marketingtype}. Sure, I can help give you an ${usermenuselection} to ${typeofservice} your place. 124 | // Can you please tell me if you live in a house or an apartment?`); 125 | 126 | // } else if (typeofservice && usermenuselection && !marketingtype && !construct) { 127 | // // they didn't mention what they responded to, and we provide them a lead in to their selected action and type of service 128 | // console.log('[Default Welcome Intent]- ' + ' typeofservice: ' + typeofservice + ' usermenuselection: ' + usermenuselection); 129 | // agent.add(`Hey there!, I'd love to give you an ${usermenuselection} to ${typeofservice} your place. 130 | // Do you live in a house or an apartment?`); 131 | 132 | // } else if (typeofservice && !usermenuselection && !marketingtype && !construct) { 133 | // // they didn't mention what they responded to, and we provide them a lead in to their selected action and type of service 134 | // console.log('[Default Welcome Intent] typeofservice: ' + typeofservice); 135 | // agent.add(`Claro, I'd love to ${typeofservice} your place. 136 | // Do you live in a house or an apartment?`); 137 | 138 | // } else if (marketingtype && !typeofservice && !usermenuselection && !construct) { 139 | // console.log('[Default Welcome Intent] ' + agent.parameters.marketingtype); 140 | // marketingtype = agent.parameters.marketingtype; 141 | // agent.add('Hi, thanks for checking out our ' + marketingtype + '! Would you like me to ' + menu[0] + ', ' + menu[1] + ' or ' + menu[2]); 142 | // } else { 143 | // agent.add(`Hi, thanks for texting! Would you like me to ${menu[0]}, ${menu[1]}, or ${menu[2]}?`); 144 | // } 145 | // we didn't get anything from them so we just give ask what service they want. 146 | // let the default handle for now 147 | // agent.add(`Glad you liked my ${marketingtype}. Sure, I can help give you an ${usermenuselection} to ${typeofservice} your place.`) 148 | // console.log('[Welcome] Service.marketingtype: ' + marketingtype); 149 | 150 | } 151 | 152 | function mainMenu() { 153 | console.log('[Main Menu' + agent.parameters) 154 | //agent.add(`Of Course! Would you like me to ${menu[0]}, ${menu[1]} or ${menu[2]}?`); 155 | } 156 | 157 | function showListOfServices(agent) { 158 | console.log('[showListOfServices] Agent Parmameters: ' + agent.parameters); 159 | 160 | //simple for now 161 | agent.add(`Absolutely! ${services[0]}, and ${services[1]}. Additionally, we offer ${services[2]}. \n 162 | Would you like to ${menu[0]} or ${menu[2]}?`); 163 | 164 | //if asking for a specific service add logic and uncomment this 165 | //agent.add(`Absolutely! ${services[0]}. Would you like me to ${menu[1]} or ${menu[2]} for you?`); 166 | 167 | } 168 | 169 | 170 | // Highest Importance 171 | function serviceInfoServiceType(agent) { 172 | console.log('[serviceInfo] Agent Parmameters: ' + agent.parameters); 173 | // context in ---> marketingytpe, typeofservice, construct, usermenuselection 174 | // context out --> typeofservice 175 | 176 | 177 | // check if contexts were stored in objects before assigning 178 | // then assign if needed 179 | // if (agent.parameters.typeofservice) { 180 | // typeofservice = agent.parameters.typeofservice; 181 | // service.typeofservice = typeofservice; 182 | // } 183 | 184 | 185 | // if (!service.construct) { 186 | // if (agent.parameters.construct) { 187 | // construct = agent.parameters.construct; 188 | // service.construct = construct; 189 | // } 190 | // } 191 | 192 | // if (!user.usermenuselection) { 193 | // if (agent.parameters.usermenuselection) { 194 | // usermenuselection = agent.parameters.usermenuselection; 195 | // user.usermenuselection = usermenuselection; 196 | // } 197 | // } 198 | 199 | 200 | 201 | 202 | // // in case we need to pass service.obj 203 | // if (service.typeofservice && service.construct && user.usermenuselection && user.marketingtype) { 204 | // // custom message showing thanks for responding to the ad and providing them a lead in to their selected action 205 | // console.log('[Service Info - Service Type] ' + ' typeofservice: ' + typeofservice + ' usermenuselection: ' + usermenuselection + ' marketingtype: ' + marketingtype); 206 | // agent.add(`Of course I can ${service.typeofservice} your ${service.construct}! Can you tell me how many bedrooms you have?`); 207 | 208 | // } 209 | 210 | 211 | // if (typeofservice && construct && usermenuselection && marketingtype) { 212 | // // custom message showing thanks for responding to the ad and providing them a lead in to their selected action 213 | // console.log('[Service Info - Service Type] ' + ' typeofservice: ' + typeofservice + ' usermenuselection: ' + usermenuselection + ' marketingtype: ' + marketingtype); 214 | // agent.add(`Of course I can ${typeofservice.orginal} your ${construct}! Can you tell me how many bedrooms you have?`); 215 | 216 | // }else if (typeofservice && construct && usermenuselection && !marketingtype) { 217 | // // they didn't mention what they responded to, and we provide them a lead in to their selected action and type of service 218 | // console.log('[Service Info - Service Type] ' + ' typeofservice: ' + typeofservice + ' construct: ' + construct + ' usermenuselection: ' + usermenuselection); 219 | // agent.add(`Sure, I can help you ${usermenuselection} for ${typeofservice}. 220 | // Can you please tell me if you live in a house or an apartment? I also do dorms.`); 221 | 222 | // } else if (typeofservice && construct && !usermenuselection && !marketingtype) { 223 | // // they didn't mention what they responded to, and we provide them a lead in to their selected action and type of service 224 | // console.log('[Service Info - Service Type] ' + ' typeofservice: ' + typeofservice + ' construct: ' + construct); 225 | // agent.add(`Of course I can ${typeofservice}! \n 226 | // Can you tell me if you live in a house, apartment or single room like a dorm?`); 227 | 228 | // } else if (typeofservice && !construct && !usermenuselection && !marketingtype) { 229 | // // they didn't mention what they responded to, and we provide them a lead in to their selected action and type of service 230 | // console.log('[Service Info - Service Type] ' + ' typeofservice: ' + typeofservice); 231 | // agent.add(`Of course I help ${typeofservice} your place! \n 232 | // Can you tell me if you live in a house, apartment or single room like a dorm?`); 233 | 234 | // } else { 235 | // // we didn't get anything from them so we just give ask what service they want. 236 | // // let the default handle for now 237 | // agent.add('Can you tell me if you live in a house or an apartment?'); 238 | 239 | // } 240 | 241 | // SETTERS 242 | // sesh.service = service; 243 | // user.usermenuselection = usermenuselection; 244 | // user.marketingtype = marketingtype; 245 | //console.log('[Service Info] service obj: ' + typeofservice); 246 | 247 | // AGENT Says 248 | // Apt, house or condo? 249 | 250 | } 251 | 252 | // follow up -intent 253 | 254 | function serviceInfoConstruct(agent) { 255 | console.log('[Service Info - Construct] Agent Parameters: ' + agent.parameters); 256 | 257 | // if (!service.construct) { 258 | // if (agent.parameters.construct) { 259 | // construct = agent.parameters.construct; 260 | // service.construct = construct; 261 | // } 262 | // } 263 | // console.log('[Service Info - Construct] service.construct: ' + service.construct + 'service.typeofservice: ' + service.typeofservice); 264 | 265 | agent.add(`how many bedrooms are we cleaning?`); 266 | } 267 | 268 | function serviceInfoNR(agent) { 269 | console.log('[Service Info - NR] Agent Parameters: ' + agent.parameters); 270 | agent.add(`[Service Info - NR] How many bathrooms?`); 271 | // if (agent.parameters.numrooms) { 272 | // let numrooms = agent.parameters.numrooms; 273 | // service.numrooms = numrooms; 274 | // agent.add(`Ok, ${numrooms} bedrooms. how many bathrooms?`); 275 | // } else { 276 | // agent.add(`How many bathrooms?`); 277 | // } 278 | //agent.add(`How many bathrooms?`); 279 | } 280 | 281 | function serviceInfoNB(agent) { 282 | console.log('[Service Info - NB] Agent Parameters: ' + agent.parameters); 283 | agent.add(`[Service Info - NB] How many Square Feet approximately?`); 284 | 285 | // if (agent.parameters.numbaths) { 286 | // numbaths = agent.parameters.numbaths; 287 | // service.numbaths = numbaths; 288 | // agent.add(`Ok, thanks. that's ${numbaths} bathrooms. We give discounts for smaller spaces. 289 | // \n Can you please tell me the approximate square feet?`); 290 | // } else if (agent.parameters.numrooms && agent.parameters.numbaths) { 291 | // numbaths = agent.parameters.numbaths; 292 | // service.numbaths = numbaths; 293 | // let tempnumrms = service.numrooms; 294 | // agent.add(`Awesome! I have ${tempnumrms} bedrooms and ${numbaths} bathrooms. We give discounts for smaller spaces. \n 295 | // Can you please tell me the approximate square feet?`); 296 | // } 297 | // agent.add(`How many square feet?`); 298 | } 299 | 300 | function serviceInfoSF(agent) { 301 | console.log('[Service Info - SQFT] Agent Parameters: ' + agent.paramteters); 302 | // if (agent.paramteters) { 303 | // const sqft = agent.parameters.sqft; 304 | // agent.add(`Ok ${sqft} square feet sounds good. I offer discounts for people who are generally clean and clutter free because I do the job faster. Can you rank yourself and family from 1-3? 1 being Oscar Madison from the Odd Couple and 3 being Monica Geller from Friends..🤣`); 305 | // } 306 | // const sqft = agent.parameters.sqft; 307 | agent.add(`[Service Info - SQFT] We give discounts if you have no laundry on the floor or dishes in the sink. On a scale of 1-3, with 3 being the least clutter how would you rank your space?. `); 308 | } 309 | 310 | function serviceInfoCF(agent) { 311 | console.log('[Service Info - CleanFactor] Agent Parameters: ' + agent.paramteters); 312 | // TODO get cleanfactor 313 | agent.add(`[Service Info - CleanFactor] Got it! Can you please give me your first and last name along with your email address for the estimate.`); 314 | } 315 | 316 | 317 | function getUserFirstName(agent) { 318 | //let name = agent.parameters.userfirstname.name; 319 | //user.name = name; 320 | //console.log('[getUserFirstName] set user.name : ', user.name); 321 | //agent.add("Hello " + name + ", Do you need an estimate? If you have any questions please let me know" ); 322 | //agent.setContext({ name: 'userfirstname', lifespan: 5, parameters: { userfirstname: name}}); 323 | } 324 | 325 | function fallback(agent) { 326 | agent.add(`[fallback] I didn't understand`); 327 | agent.add(`[fallback] I'm sorry, can you try again?`); 328 | } 329 | 330 | // // Uncomment and edit to make your own intent handler 331 | // // uncomment `intentMap.set('your intent name here', yourFunctionHandler);` 332 | // // below to get this function to be run when a Dialogflow intent is matched 333 | // function yourFunctionHandler(agent) { 334 | // agent.add(`This message is from Dialogflow's Cloud Functions for Firebase editor!`); 335 | // agent.add(new Card({ 336 | // title: `Title: this is a card title`, 337 | // imageUrl: 'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png', 338 | // text: `This is the body text of a card. You can even use line\n breaks and emoji! 💁`, 339 | // buttonText: 'This is a button', 340 | // buttonUrl: 'https://assistant.google.com/' 341 | // }) 342 | // ); 343 | // agent.add(new Suggestion(`Quick Reply`)); 344 | // agent.add(new Suggestion(`Suggestion`)); 345 | // agent.setContext({ name: 'weather', lifespan: 2, parameters: { city: 'Rome' }}); 346 | // } 347 | 348 | // // Uncomment and edit to make your own Google Assistant intent handler 349 | // // uncomment `intentMap.set('your intent name here', googleAssistantHandler);` 350 | // // below to get this function to be run when a Dialogflow intent is matched 351 | // function googleAssistantHandler(agent) { 352 | // let conv = agent.conv(); // Get Actions on Google library conv instance 353 | // conv.ask('Hello from the Actions on Google client library!') // Use Actions on Google library 354 | // agent.add(conv); // Add Actions on Google library responses to your agent's response 355 | // } 356 | // // See https://github.com/dialogflow/fulfillment-actions-library-nodejs 357 | // // for a complete Dialogflow fulfillment library Actions on Google client library v2 integration sample 358 | 359 | // Run the proper function handler based on the matched Dialogflow intent name 360 | let intentMap = new Map(); 361 | intentMap.set('Default Welcome Intent', welcome); // typeofmarketing 362 | intentMap.set('Default Fallback Intent', fallback); 363 | 364 | // Main Menu 365 | intentMap.set('Main Menu', mainMenu); // menu[0] ... 'estimate', 'schedule', 'list' 366 | // Services List 367 | intentMap.set('List Services', showListOfServices); // cleaning, extra, pro 368 | 369 | // Estimate 370 | //intentMap.set('Create Estimate', createEstimate); 371 | 372 | // Schedule 373 | //intentMap.set('Schedule Appointment', scheduleAppointment); 374 | 375 | // Service Info - typeofservice 376 | intentMap.set('Service Info - Service Type', serviceInfoServiceType); // cleaning, extra, pro 377 | // construct 378 | intentMap.set('Service Info - Construct', serviceInfoConstruct); // house, apartment 379 | // numbeds 380 | intentMap.set('Service Info - Construct - NumRooms', serviceInfoNR); // int 381 | //numbaths 382 | intentMap.set('Service Info - Construct - NumRooms - NumBaths', serviceInfoNB); //int 383 | //sqft 384 | intentMap.set('Service Info - Construct - NumRooms - NumBaths - SQFT', serviceInfoSF); // int 385 | //clean factor // 386 | intentMap.set('Service Info - Construct - NumRooms - NumBaths - SQFT - CleanFactor', serviceInfoCF); //oint 387 | 388 | 389 | // Appointment Scheduler TBD 390 | 391 | // intentMap.set('your intent name here', googleAssistantHandler); 392 | agent.handleRequest(intentMap); 393 | }); 394 | --------------------------------------------------------------------------------