diff --git a/goodbye-graffiti-agent/README.md b/goodbye-graffiti-agent/README.md new file mode 100644 index 00000000..78cbe70a --- /dev/null +++ b/goodbye-graffiti-agent/README.md @@ -0,0 +1,58 @@ +# goodbye-graffiti-agent + +## Overview + +“Goodbye Graffiti” is a demo built on Dialogflow CX inspired by a number of online forms created by city councils and local governments to report graffiti vandalism. + +The demo features: +1. [Generative Fallback](https://cloud.google.com/dialogflow/cx/docs/concept/generative-fallback) to generate virtual agent responses when end-user input does not match an intent or parameter for form filling. +1. [Generators](https://cloud.google.com/dialogflow/cx/docs/concept/generators) to greet the user and provide a summary of the report. +1. [Google Maps Geocoding API](https://developers.google.com/maps/documentation/geocoding/requests-geocoding) to lookup latitude/longitude and formatted address of the graffiti location +1. The [address collection prebuilt component](https://cloud.google.com/dialogflow/cx/docs/concept/prebuilt-component/address-collection) to assist the user with a step-by-step address collection process in case the request to the Geocode APIs returns zero results or the returned address is incorrect. +1. [Maps Static API](https://developers.google.com/maps/documentation/maps-static/overview) to visualize the location of the graffiti on the map +1. [Dialogflow CX Phone Gateway and Call Companion](https://cloud.google.com/dialogflow/cx/docs/concept/integration/phone-gateway) to provide a telephone interface to the agent and a a multi-modal (voice + visual) customer experience +1. [Cloud Functions](https://cloud.google.com/functions/docs/configuring) to run the webhook service required to integrate the agent with Google Maps APIs. + +## Setup your Google Cloud Project +1. Setup your [Google Cloud Project](https://cloud.google.com/dialogflow/cx/docs/quick/setup) +1. To use Google Maps Platform you must enable the [Geocoding API](https://console.cloud.google.com/apis/library/geocoding-backend.googleapis.com?utm_source=Docs_EnableAPIs&utm_content=Docs_geocoding-backend&_gl=1*1syfwbs*_ga*MTMxNzQwMTEyNS4xNjkyMDE1OTQ1*_ga_NRWSTWS78N*MTY5MjAxNTk0Ni4xLjEuMTY5MjAxNjk4Ni4wLjAuMA..) and [Maps Static API](https://console.cloud.google.com/apis/library/static-maps-backend.googleapis.com?utm_source=Docs_EnableAPIs&utm_content=Docs_static-maps-backend&_gl=1*13fpvaq*_ga*MTMxNzQwMTEyNS4xNjkyMDE1OTQ1*_ga_NRWSTWS78N*MTY5MjAxNTk0Ni4xLjEuMTY5MjAxNjk4Ni4wLjAuMA..). +1. You must have at least one API key associated with your project. Go to the Google Maps Platform > Credentials [page](https://console.cloud.google.com/project/_/google/maps-apis/credentials?utm_source=Docs_CreateAPIKey&utm_content=Docs_static-maps-backend&_gl=1*8gesr0*_ga*MTMxNzQwMTEyNS4xNjkyMDE1OTQ1*_ga_NRWSTWS78N*MTY5MjAxNTk0Ni4xLjEuMTY5MjAxNzI2MS4wLjAuMA..) and create an [API key](https://developers.google.com/maps/documentation/maps-static/get-api-key#creating-api-keys). +1. Open file `index.js` located under `maps-function` folder. You must include your API key with both the Geocoding API and Maps Static API requests. Replace `YOUR_API_KEY` with your API key. +1. Enable the Cloud Functions, Cloud Build, Artifact Registry, Cloud Run, Dialogflow API and Cloud Logging [APIs](https://console.cloud.google.com/flows/enableapi?apiid=cloudfunctions.googleapis.com,%20%20%20%20%20cloudbuild.googleapis.com,artifactregistry.googleapis.com,%20%20%20%20%20run.googleapis.com,logging.googleapis.com&redirect=https://cloud.google.com/functions/docs/create-deploy-http-nodejs&_ga=2.176777133.982063149.1692018523-870547608.1691743190) +1. Install and initialize the [gcloud CLI](https://cloud.google.com/sdk/docs/install). + +## Deploy the Cloud Function +To deploy the function, run the `gcloud functions deploy` command in the `maps-function` directory: +``` +gcloud functions deploy lookupPlace --runtime=nodejs20 --region=REGION --source=. --entry-point=lookupPlace --trigger-http --allow-unauthenticated +``` + +Replace `REGION` with the name of the Google Cloud region where you want to deploy your function (for example, `us-west1`). +The optional `--allow-unauthenticated` flag lets you reach your function without authentication. + +After the function deploys, note the url property from the output of the gcloud functions deploy command, or retrieve it with the command `gcloud functions describe lookupPlace --region REGION`. Replace `REGION` with the name of the Google Cloud region where you deployed your function. You will need the url when configuring the webhook in Dialogflow CX. + +## Create the agent and configure the webhook +1. In your browser, navigate to the [Dialogflow CX console](https://dialogflow.cloud.google.com/cx/projects) +1. Create a new agent (select the option **Build your own agent**). You must create the agent in the **global** region as this is the only region where the Phone Gateway integration is currently supported. You cannot change region once the agent is deployed. +1. [Restore](https://cloud.google.com/dialogflow/cx/docs/concept/agent#export) the Goodbye Graffiti agent exported in the JSON package file format (agent.zip) +1. Once you have successfully restored the agent, under the **Manage** tab open the definition of the **lookup-place** webhook +1. Enter your function URL as the webhook URL and click Save. + +![Dialogflow CX Webhook](images/webhook.png) + +## Configure the phone gateway + +1. Navigate to **Integrations** and click **Manage** on the CX Phone Gateway panel. +1. Setup the [phone gateway](https://cloud.google.com/dialogflow/cx/docs/concept/integration/phone-gateway#setup) integration. +1. Select a phone number, then click **Show more settings** to enable the call companion feature . +1. Copy the phone number. + +## Test the agent + +In your browser using Google Voice or directly from your phone, call the number. You will receive a message with the Cloud Companion URL. Click the link to open the UI and test the agent. + +![Demo](images/demo.png) + +Congratulations, you've successfully deployed the Goodbye Graffiti demo! + diff --git a/goodbye-graffiti-agent/agent.zip b/goodbye-graffiti-agent/agent.zip new file mode 100644 index 00000000..c6e6a0e6 Binary files /dev/null and b/goodbye-graffiti-agent/agent.zip differ diff --git a/goodbye-graffiti-agent/images/demo.png b/goodbye-graffiti-agent/images/demo.png new file mode 100644 index 00000000..37ea962e Binary files /dev/null and b/goodbye-graffiti-agent/images/demo.png differ diff --git a/goodbye-graffiti-agent/images/webhook.png b/goodbye-graffiti-agent/images/webhook.png new file mode 100644 index 00000000..1b660e8b Binary files /dev/null and b/goodbye-graffiti-agent/images/webhook.png differ diff --git a/goodbye-graffiti-agent/maps-function/index.js b/goodbye-graffiti-agent/maps-function/index.js new file mode 100644 index 00000000..26ae167d --- /dev/null +++ b/goodbye-graffiti-agent/maps-function/index.js @@ -0,0 +1,129 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Responds to any HTTP request. + * + * @param {!express:Request} req HTTP request context. + * @param {!express:Response} res HTTP response context. + */ + + var axios = require('axios'); + + exports.lookupPlace = async (req, res) => { + + let tag = req.body.fulfillmentInfo.tag; + var payload = {}; + var caller_id = ''; + + if (!!tag) { + switch (tag) { + //BEGIN findPlace + + case 'geocode': + console.log(tag + ' was triggered.'); + var results = []; + + // Check if required params have been populated + if (!(req.body.sessionInfo && req.body.sessionInfo.parameters)) { + return res.status(404).send({ error: 'Not enough information.' }); + } + + // Retrieve caller_id if it's a phone call + if ( typeof req.body.payload !== 'undefined' && req.body.payload !== 'undefined' ) { + caller_id = req.body.payload.telephony.caller_id; + } + + // Set location to the location param value collected from the user. + // Location must be a place name or an address. Reserved characters (for example the plus sign "+") must be URL-encoded. + location = encodeURI(req.body.sessionInfo.parameters['location'].original); + console.log('caller_id: ' + caller_id); + + // invokes Geocode APIs and looks for a match + try { + var config = { + method: 'get', + url: 'https://maps.googleapis.com/maps/api/geocode/json?address=' + location + '&key=YOUR_API_KEY', + headers: { } + }; + + axios(config) + .then(function (response) { + //at least one result + if(response.data.results.length > 0) { + + for(var i in response.data.results){ + // geocoder returned several results + var result = response.data.results[i]; + results.push(result); + } + + // single match scenario. Build map. Either ways transition to the same target page. Disambiguation in Dialogflow + if (results.length == 1){ + var lat = results[0].geometry.location.lat; + var lng = results[0].geometry.location.lng; + var formatted_address = results[0].formatted_address; + + // config static map + var map_img = 'https://maps.googleapis.com/maps/api/staticmap?center=' + formatted_address + '&zoom=14&size=600x300&markers=color:red|' + lat + ',' + lng +'&key=YOUR_API_KEY' + payload = { + "richContent": [ + { + "type": "image", + "imageUrl": map_img + } + ] + }; + + } + + } else { + //handle ZERO_RESULTS + formatted_address = ""; + } + // send fullfilment back to agent + res.status(200).send({ + sessionInfo: { + parameters: { + formatted_address: formatted_address, + caller_id: caller_id + } + }, + fulfillmentResponse: { + messages: [{ + payload: payload + }] + } + }); + }); + + } catch (error) { + res.status(500).send(error); + console.log(error); + } + + break; + + + + default: + console.log('default case called'); + res.status(200).end(); + break; + } + } +}; + \ No newline at end of file diff --git a/goodbye-graffiti-agent/maps-function/package.json b/goodbye-graffiti-agent/maps-function/package.json new file mode 100644 index 00000000..a2c8682b --- /dev/null +++ b/goodbye-graffiti-agent/maps-function/package.json @@ -0,0 +1,16 @@ +{ + "name": "maps-function", + "version": "1.0.0", + "description": "Webhook service required to integrate the goodbye-graffiti agent with Google Maps Geocode and Static API", + "main": "index.js", + "dependencies": { + "@google-cloud/functions-framework": "^2.0.0", + "axios": "^0.24.0" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "alessiasacchi", + "license": "ISC" +}