Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions _emulator/firebase.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extensions": {
"firestore-record-user-acknowledgements": "../firestore-record-user-acknowledgements",
"firestore-bundle-server": "../firestore-bundle-server"
"firestore-bundle-server": "../firestore-bundle-server",
"firestore-dialogflow-fulfillment": "../firestore-dialogflow-fulfillment"
},
"storage": {
"rules": "storage.rules"
Expand Down Expand Up @@ -35,7 +36,11 @@
},
"hosting": {
"public": "dist",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "/bundles/*",
Expand Down
71 changes: 71 additions & 0 deletions firestore-dialogflow-fulfillment/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
firebase-debug.log*
firebase-debug.*.log*

# Firebase cache
.firebase/

# Firebase config

# Uncomment this if you'd like others to create their own Firebase project.
# For a team working on the same Firebase project(s), it is recommended to leave
# it commented so all members can deploy to the same project(s) in .firebaserc.
# .firebaserc

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

/functions/lib/*

# Service file
extensions-testing-firebase.json
11 changes: 11 additions & 0 deletions firestore-dialogflow-fulfillment/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Version 0.0.3

feat: additional training phrase

## Version 0.0.2

fix: Include build sourcecode.

## Version 0.0.1

chore: Initial release
47 changes: 47 additions & 0 deletions firestore-dialogflow-fulfillment/POSTINSTALL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## Setting up DialogFlow Fulfillment

First, copy this URL, which is the URL of the DialogFlow webhook: `https://${param:LOCATION}-${param:PROJECT_ID}.cloudfunctions.net/ext-${param:EXT_INSTANCE_ID}-dialogflowFulfillment`

Next, go to the DialogFlow console [here](https://dialogflow.cloud.google.com/#/agent/${param:PROJECT_ID}/fulfillment), enable the Webhook and add this URL as the Fulfillment webhook URL.

## Give the extension access to your calendar

To allow the extension to create events, you need to give it access to the calendar `${param:CALENDAR_ID}`.

To do this, copy the extension service account principal email address:

```
ext-${param:EXT_INSTANCE_ID}@extensions-sample.iam.gserviceaccount.com
```

which can be found in the [Service Accounts page](https://console.cloud.google.com/iam-admin/serviceaccounts) in the Google Cloud Console.

Next, go to [your calendar](https://calendar.google.com/calendar/u/0/r/settings) Sharing settings, and share it with the service account email, make sure to give it **Make changes to events** permission.

## Using the extension

Call `newConversation` with a `message` to start a new conversation, which will return a `conversationId` that you can use to send messages to the same conversation.

```ts
export async function newConversation(message: string): Promise<string> {
const result = await httpsCallable<{ message: string }, string>(
functions,
"ext-${param:EXT_INSTANCE_ID}-newConversation"
)({ message });
return result.data;
}
```

To add new messages to the conversation, call `newMessage` with the `conversationId` and `message`.

```ts
export async function newMessage(
conversationId: string,
message: string
): Promise<void> {
await httpsCallable<{ conversationId: string; message: string }, void>(
functions,
"ext-${param:EXT_INSTANCE_ID}-newMessage"
)({ conversationId, message });
}
```
Empty file.
81 changes: 81 additions & 0 deletions firestore-dialogflow-fulfillment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Firestore DialogFlow Fulfillment

This extension integrates with DialogFlow through Firestore, it can book meetings on your calendar from a DialogFlow conversation.

You don't need to interact with DialogFlow in your app as the extension do it for you and provide the conversation replies from DialogFlow in a Firestore collection.

## Install The Extension

Make sure `firebase-tools` is installed: `npm i -g firebase-tools`.

To install the extension, run:

```bash
firebase ext:install path/to/extension --project=extensions-testing
```

### DialogFlow agent setup

Before you can use the extension, you need to have a DialogFlow agent. The extension provides you with a HTTP function that you can call to create an agent for you, so you don't to do it manually.

To create and agent, you can call the `ext-firestore-dialogflow-fulfillment-createDialogflowAgent` function through the [Google Cloud CLI](https://cloud.google.com/sdk/gcloud):

```bash
gcloud config set project PROJECT_ID
gcloud config set functions/region LOCATION
gcloud functions call ext-firestore-dialogflow-fulfillment-createDialogflowAgent --data '{"data":""}'
```

This will create a new DialogFlow agent for you. You can find the agent in the DialogFlow console [here](https://dialogflow.cloud.google.com/#/agents).

### Setting up DialogFlow Fulfillment

The extension will deploy a HTTP function that will be used as a Fullfilment webhook for DialogFlow. The function will look like this, with the `PROJECT_ID` replaced with your project ID, and the `LOCATION` replaced with your project's default Cloud Functions location:

```bash
https://{LOCATION}-{PROJECT_ID}.cloudfunctions.net/ext-firestore-dialogflow-fulfillment-dialogflowFulfillment
```

Provide the URL to DialogFlow as the Fulfillment webhook URL.

### Give the extension access to your calendar

To allow the extension to create events, you need to give it access to the calendar `${param:CALENDAR_ID}`. To do this, copy the extension service account principal email address from the [Service Accounts page](https://console.cloud.google.com/iam-admin/serviceaccounts) in the Google Cloud Console, which starts with `ext-firestore-dialogflow`. Then, go to your calendar Sharing settings, and add the email with **Make changes to events** permission.

## Using the extension

Call `newConversation` with a `message` to start a new conversation, which will return a `conversationId` that you can use to send messages to the same conversation.

```ts
export async function newConversation(message: string): Promise<string> {
const result = await httpsCallable<{ message: string }, string>(
functions,
"ext-firestore-dialogflow-fulfillment-newConversation"
)({ message });
return result.data;
}
```

To add new messages to the conversation, call `newMessage` with the `conversationId` and `message`.

```ts
export async function newMessage(
conversationId: string,
message: string
): Promise<void> {
await httpsCallable<{ conversationId: string; message: string }, void>(
functions,
"ext-firestore-dialogflow-fulfillment-newMessage"
)({ conversationId, message });
}
```

## Development

1. `cd firestore-dialogflow-fulfillment/functions` && `npm run build`
2. `cd firestore-dialogflow-fulfillment/demo` && `npm run dev`
3. `cd _emulator` && `firebase emulators:start`

(Note: Been using https://ngrok.com/ to pipe the webhook messages to the emulator - just needs installing and setting up on the port functions are running on).

Use `lsof -t -i:4001 -i:8080 -i:9000 -i:9099 -i:9199 -i:8085 | xargs kill -9` to kill emulators.
24 changes: 24 additions & 0 deletions firestore-dialogflow-fulfillment/demo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
12 changes: 12 additions & 0 deletions firestore-dialogflow-fulfillment/demo/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Firestore DialogFlow Demo</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
23 changes: 23 additions & 0 deletions firestore-dialogflow-fulfillment/demo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "demo",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"firebase": "^9.14.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.0.24",
"@types/react-dom": "^18.0.8",
"@vitejs/plugin-react": "^2.2.0",
"typescript": "^4.6.4",
"vite": "^3.2.3"
}
}
60 changes: 60 additions & 0 deletions firestore-dialogflow-fulfillment/demo/src/firebase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { initializeApp } from "firebase/app";
import { getAuth, connectAuthEmulator, signInAnonymously } from "firebase/auth";
import {
getFirestore,
connectFirestoreEmulator,
collection,
onSnapshot,
QuerySnapshot,
DocumentData,
orderBy,
query,
} from "firebase/firestore";
import {
getFunctions,
connectFunctionsEmulator,
httpsCallable,
} from "firebase/functions";

export const app = initializeApp({
projectId: "extensions-testing",
apiKey: "123",
});
export const auth = getAuth(app);
export const firestore = getFirestore(app);
export const functions = getFunctions(app);

connectAuthEmulator(auth, "http://localhost:9099");
connectFirestoreEmulator(firestore, "localhost", 8080);
connectFunctionsEmulator(functions, "localhost", 5001);

export function signIn() {
return signInAnonymously(auth);
}

export async function newConversation(message: string): Promise<string> {
const result = await httpsCallable<{ message: string }, string>(
functions,
"ext-firestore-dialogflow-fulfillment-newConversation"
)({ message });
return result.data;
}

export async function newMessage(
conversationId: string,
message: string
): Promise<void> {
await httpsCallable<{ conversationId: string; message: string }, void>(
functions,
"ext-firestore-dialogflow-fulfillment-newMessage"
)({ conversationId, message });
}

export function streamMessages(
id: string,
cb: (snapshot: QuerySnapshot<DocumentData>) => void
) {
const ref = collection(firestore, "conversations", id, "messages");
const q = query(ref, orderBy("created_at", "asc"));
return onSnapshot(q, cb);
}
Loading