Skip to content

Instantly share code, notes, and snippets.

@CodingDoug
Last active May 6, 2021 14:35
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save CodingDoug/51030c85894b35a3495f548b1b5b0aff to your computer and use it in GitHub Desktop.
Save CodingDoug/51030c85894b35a3495f548b1b5b0aff to your computer and use it in GitHub Desktop.
Building an assistant (chatbot) that translates languages, integrated with Slack

Building an assistant (chatbot) that translates languages, integrated with Slack

If you're trying to do this, you came to the right place!

See this code in action here: https://twitter.com/CodingDoug/status/953031540811825152

Setup

  1. Create a Firebase project at the Firebase Console

  2. Create a Dialogflow agent the Dialogflow console that uses the Firebase project that was just created (select it from the dropdown).

  3. Import the prebuilt agent called "Translate" by selecting "Prebuilt Agents" on the left and scrolling down to find "Translate". This will create some intents automatically - you don't have to create any intents or entities. Take a look at them to see how conversation works.

  4. Using the Firebase CLI, initialize a new project space with Functions, and select the Firebase project you created earlier.

  5. Install the Actions on Google and Cloud Translate API libraries for node:

    cd functions
    npm install actions-on-google @google-cloud/translate
    
  6. Edit functions/src/index.ts and copy the code from index.ts in this gist into it, replacing its contents.

  7. Deploy the function with firebase deploy

  8. In the deployment output, copy the function URL. It will look something like this:

    https://us-central1-your-project.cloudfunctions.net/fulfill

  9. In the Dialogflow console, select "Fulfillment" on the left,enable the webhook with the switch, then paste the function URL into the URL field.

  10. Select "Intents" on the left. For every intent that was imported, select it, scroll to the very bottom, check the "Use webhook" checkbox under "Fulfillment", and click "Save" at the top.

  11. In the Google Cloud console for your Firebase project, enable the Cloud Translate API. This requires billing to be enabled on your project.

    (If you don't enable this API, then first time the translate API is called by your function, it will leave an error in the Functions log with a URL to follow to enable the Google Cloud Translate API. Copy that URL and load it into your browser to enable the API.)

  12. At this point, you should be able to type translation requests using the Dialogflow test panel on the right in the Dialogflow console.

Enabling Slack integration

  1. Create a workspace in Slack, or use an existing workspace.

  2. To enable Slack integration with Dialogflow, choose "Integrations" on the left, then enable Slack. Follow the instructions and give permission to Dialogflow to operate a bot on your workspace.

  3. When you enable the test bot, it should appear as an app in your Slack workspace with the name "Dialogflow Bot". You can direct message this bot to get translations.

Helpful documentation

// Copyright 2018 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
//
// https://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.
import * as functions from 'firebase-functions'
import { DialogflowApp } from 'actions-on-google'
import * as Translate from '@google-cloud/translate'
const translate = new Translate({
projectId: functions.config().firebase.projectId,
})
const actionMap = new Map()
actionMap.set('translate.text', translateText)
const languages = {
'French': 'fr',
'Russian': 'ru',
'Spanish': 'es',
'Japanese': 'ja',
'Chinese': 'zh',
'Dutch': 'nl',
'English': 'en',
'German': 'de',
'Italian': 'it',
'Korean': 'ko'
}
export const fulfill = functions.https.onRequest((request, response) => {
const app = new DialogflowApp({request, response})
app.handleRequest(actionMap)
})
function translateText(app) {
let text = app.getArgument('text')
let langTo = app.getArgument('lang-to')
let langFrom = app.getArgument('lang-from')
console.log(app.getContext('translate-text'))
if (!text) {
const arg = app.getContextArgument('translate-text', 'lastText')
if (arg) { text = arg.value }
}
if (!langTo) {
const arg = app.getContextArgument('translate-text', 'lastLangTo')
if (arg) { langTo = arg.value }
}
if (!langFrom) {
const arg = app.getContextArgument('translate-text', 'lastLangFrom')
if (arg) { langFrom = arg.value }
if (!langFrom) { langFrom = 'English' }
}
app.setContext('translate-text', 2, {
lastText: text,
lastLangTo: langTo,
lastLangFrom: langFrom
})
console.log(`text: ${text}`)
console.log(`lang-to: ${langTo}`)
console.log(`lang-from: ${langFrom}`)
if (!text) {
ask(app, "What would you like to translate?")
}
else if (!langTo) {
ask(app, "What language would you like to translate to?")
}
else {
const options = { to: languages[langTo] }
if (langFrom) {
options['from'] = languages[langFrom]
}
translate.translate(text, options)
.then(results => {
const translation = results[0]
console.log(`translation: ${translation}`)
tell(app, translation)
})
.catch(error => {
console.error(error)
const message = "Sorry, I couldn't translate that."
tell(app, message)
})
}
}
// Warning! I'm using private APIs below to gain access to the data field
// in the Dialogflow response.
function tell(app, speech) {
const res = app.buildResponse_(speech, false)
res.data.slack = { text: speech }
app.doResponse_(res, 200)
}
function ask(app, question) {
const res = app.buildResponse_(question, true)
res.data.slack = { text: question }
app.doResponse_(res, 200)
}
@yiouyou
Copy link

yiouyou commented Jul 16, 2018

When trying the upper code, it shows some error. Need help, thanks!

40th line, Error: 'DialogflowApp' only refers to a type but is being used as a value here

default

@yiouyou
Copy link

yiouyou commented Jul 22, 2018

modified to index.js as below seems working

'use strict';

const functions = require('firebase-functions');
const {WebhookClient, Card, Suggestion} = require('dialogflow-fulfillment');
const Translate = require('@google-cloud/translate');

process.env.DEBUG = 'dialogflow:debug';

const projectID = JSON.parse(process.env.FIREBASE_CONFIG).projectId;

const translate = new Translate({
	projectID: projectID,
})

const languages = {
    'French': 'fr',
    'Russian': 'ru',
    'Spanish': 'es',
    'Japanese': 'ja',
    'Chinese': 'zh',
    'Dutch': 'nl',
    'English': 'en',
    'German': 'de',
    'Italian': 'it',
    'Korean': 'ko'
};

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
	const agent = new WebhookClient({request, response});
	console.log('Header: '+JSON.stringify(request.headers));
	console.log('Body: '+JSON.stringify(request.body));

	function translateText(agent) {
		let text = agent.parameters.text;
		let langTo = agent.parameters.lang_to;
		let langFrom = agent.parameters.lang_from;

		console.log('context: '+agent.getContext('translate-text'));

		if(!text) {
			const arg = agent.getContext('translate-text').parameters.lastText;
			if (arg) {
				text = arg.value;
			}
		}
		if(!langTo) {
			const arg = agent.getContext('translate-text').parameters.lastLangTo;
			if (arg) {
				langTo = arg.value;
			}
		}
		if(!langFrom) {
			const arg = agent.getContext('translate-text').parameters.lastLangFrom;
			if (arg) {
				langFrom = arg.value;
			}
			if (!langFrom) {
				langFrom = 'English';
			}
		}
		agent.setContext('translate-text', 2, {
			lastText: text,
			lastLangTo: langTo,
			lastLangFrom: langFrom
		});
		console.log(`text: ${text}`);
		console.log(`lang_to: ${langTo}`);
		console.log(`lang_from: ${langFrom}`);

		if (!text) {
			agent.add("What would you like to translate?");
		} else if (!langTo) {
			agent.add("What language would you like to translate to?");
		} else {
			const options = { to: languages[langTo] };
			if (langFrom) {
				options['from'] = languages[langFrom];
			}
			return translate.translate(text, options)
			.then(results => {
				const translation = results[0];
				console.log(`translation: ${translation}`);
				//agent.add("Done translation!");
				agent.add(`${translation}`);
				return Promise.resolve();
			})
			.catch(error => {
				console.error(error);
				agent.add("Sorry, I couldn't translate that!");
				agent.add("Could you try again?");
				return Promise.resolve();
			});
		}
		return true;
	}

	let intentMap = new Map();
	intentMap.set('translate.text', translateText);
	agent.handleRequest(intentMap);
});

@ialgarnawi
Copy link

I'm getting the following errors while trying to run "firebase deploy" to deploy the function:

src/index.ts(20,19): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.
src/index.ts(41,21): error TS2693: 'DialogflowApp' only refers to a type, but is being used as a value here.
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! functions@ build: tsc
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the functions@ build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

@realowded
Copy link

Can I get a video or screenshot tutorial? as i am not familiar with GCP and this is not detailed enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment