google_assistant 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +12 -0
- data/.gitignore +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +14 -0
- data/LICENSE.md +21 -0
- data/README.md +215 -0
- data/Rakefile +11 -0
- data/google_assistant.gemspec +23 -0
- data/lib/google_assistant.rb +3 -142
- data/lib/google_assistant/argument.rb +2 -2
- data/lib/google_assistant/assistant.rb +134 -0
- data/lib/google_assistant/conversation.rb +1 -1
- data/lib/google_assistant/dialog_state.rb +52 -0
- data/lib/google_assistant/intent.rb +1 -1
- data/lib/google_assistant/version.rb +3 -0
- metadata +61 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dfd97fdfaea944f3be60796786ec4f2d9e43f8cc
|
4
|
+
data.tar.gz: a1d07df44788b57d8e4c7bf81cde7506cf41d1be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b048a4da4c4e612b38b676a0622bcda406dc1e5f2b31e94a8f0ed042432285662dee80c49c633df24d3272e5fa88eb426bc4f2111ef59441deedfd7f0e08d6f2
|
7
|
+
data.tar.gz: e3bab745f936fcee45e80936d3b45517542bc1b3f2f9deb1b5d03461b6577792ed73462ef6b4619f73a740e85f68ed311322fd31c07a4eea107bcd7441ddb907
|
data/.editorconfig
ADDED
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.1
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 Aaron Milam
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
# Google Assistant Ruby
|
2
|
+
|
3
|
+
Write Google Assistant actions in Ruby.
|
4
|
+
|
5
|
+
GoogleAssistant parses Google Assistant requests and provides the framework to respond appropriately. It works with the Google Assistant API v1.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```rb
|
12
|
+
gem "google_assistant"
|
13
|
+
```
|
14
|
+
|
15
|
+
## Get started
|
16
|
+
|
17
|
+
### Rails 4 and higher
|
18
|
+
|
19
|
+
In your routes file, allow a `POST` request to your action. All requests from Google Assistant will come here.
|
20
|
+
|
21
|
+
```rb
|
22
|
+
Rails.application.routes.draw do
|
23
|
+
post "/google_assistant" => "google_assistant#conversation"
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
Write your controller to handle the request. Use GoogleAssistant to respond to the requests.
|
28
|
+
|
29
|
+
```rb
|
30
|
+
class GoogleAssistantController < ApplicationController
|
31
|
+
|
32
|
+
def conversation
|
33
|
+
assistant_response = GoogleAssistant.respond_to(params, response) do |assistant|
|
34
|
+
assistant.intent.main do
|
35
|
+
assistant.ask(
|
36
|
+
prompt: "<speak>Hi there! Say something, please.</speak>",
|
37
|
+
no_input_prompt: [
|
38
|
+
"<speak>If you said something, I didn't hear you.</speak>",
|
39
|
+
"<speak>Did you say something?</speak>"
|
40
|
+
]
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
assistant.intent.text do
|
45
|
+
assistant.tell("<speak>I can respond, too!</speak>")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
render assistant_response
|
50
|
+
end
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
## Usage
|
55
|
+
|
56
|
+
GoogleAssistant parses the request from the Google Assistant API and helps you build your response. It takes the `params` and `response` objects in Rails and Sinatra.
|
57
|
+
|
58
|
+
```rb
|
59
|
+
assistant_response = GoogleAssistant.respond_to(params, response) do |assistant|
|
60
|
+
# Response logic goes here
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
The Google Assistant API sends a request using the `MAIN` intent for the initial request from the user. This is when the user says "OK Google, talk to #{name_of_your_app}".
|
65
|
+
|
66
|
+
```rb
|
67
|
+
assistant_response = GoogleAssistant.respond_to(params, response) do |assistant|
|
68
|
+
assistant.intent.main do
|
69
|
+
# Initial request's response goes here. You may want to introduce the app here.
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
The Google Assistant API sends a request using the `TEXT` intent for subsequent requests from the user. When your app asks for input from the user, the conversation continues. When the user finishes the conversation with "Goodbye" or your app finishes the conversation with a `tell` response, the conversation ends and any new conversations start again with the `MAIN` intent.
|
75
|
+
|
76
|
+
```rb
|
77
|
+
assistant_response = GoogleAssistant.respond_to(params, response) do |assistant|
|
78
|
+
assistant.intent.text do
|
79
|
+
# Respond to user input here.
|
80
|
+
end
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
### Ask
|
85
|
+
|
86
|
+
Request user input by sending an `ask` response. Send a prompt and a set of follow-up prompts for when the user fails to respond.
|
87
|
+
|
88
|
+
```rb
|
89
|
+
assistant.intent.main do
|
90
|
+
assistant.ask(
|
91
|
+
prompt: "<speak>Hi there! Say something, please.</speak>", # The voice prompt the user will hear.
|
92
|
+
no_input_prompt: [
|
93
|
+
"<speak>If you said something, I didn't hear you.</speak>", # You can provide a number of "no input prompts". A random
|
94
|
+
"<speak>Did you say something?</speak>" # one will be spoken if the user takes too long to respond.
|
95
|
+
]
|
96
|
+
)
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
### Tell
|
101
|
+
|
102
|
+
Send a final response, ending the conversation.
|
103
|
+
|
104
|
+
```rb
|
105
|
+
assistant.intent.text do
|
106
|
+
assistant.tell("<speak>Thanks for talking! Goodbye!</speak>") # Both SSML and plain text work here and anywhere you send a prompt.
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
### User input
|
111
|
+
|
112
|
+
GoogleAssistant allows you to read the user's input using `assistant.arguments` so that you can respond appropriately.
|
113
|
+
|
114
|
+
```rb
|
115
|
+
assistant.intent.text do
|
116
|
+
case assistant.arguments[0].text_value.downcase
|
117
|
+
when "hello"
|
118
|
+
respond_with = "Hi there!"
|
119
|
+
when "goodbye"
|
120
|
+
respond_with = "See you later!"
|
121
|
+
else
|
122
|
+
respond_with "I heard you say #{assistant.arguments[0].text_value}, but I don't know what that means."
|
123
|
+
end
|
124
|
+
|
125
|
+
assistant.tell(respond_with)
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
### Conversation state and data
|
130
|
+
|
131
|
+
You can keep data on the Conversation object. This data will be sent to the Google Assistant API, which will return that data back when the user responds. This way, you can keep ongoing information about the conversation without having to use a database to store that information.
|
132
|
+
|
133
|
+
You can also send a state value with your responses to keep track of where your conversation is at.
|
134
|
+
|
135
|
+
```rb
|
136
|
+
GoogleAssistant.respond_to(params, response) do |assistant|
|
137
|
+
assistant.intent.main do
|
138
|
+
assistant.conversation.state = "asking favorite color"
|
139
|
+
|
140
|
+
assistant.ask(
|
141
|
+
prompt: "What is your favorite color?",
|
142
|
+
no_input_prompt: ["What did you say your favorite color is?"]
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
assistant.intent.text do
|
147
|
+
if assistant.conversation.state == "asking favorite color"
|
148
|
+
assistant.conversation.data["favorite_color"] = assistant.arguments[0].text_value
|
149
|
+
|
150
|
+
assistant.conversation.state = "asking lucky number"
|
151
|
+
|
152
|
+
assistant.ask(
|
153
|
+
prompt: "What is your lucky number?",
|
154
|
+
no_input_prompt: ["What did you say your lucky number is?"]
|
155
|
+
)
|
156
|
+
elsif assistant.conversation.state == "asking lucky number"
|
157
|
+
favorite_color = assistant.conversation.data["favorite_color"]
|
158
|
+
lucky_number = assistant.arguments[0].text_value
|
159
|
+
|
160
|
+
assistant.tell("Your favorite color is #{favorite_color} and your lucky number is #{lucky_number}. Neat!")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
### SSML
|
167
|
+
|
168
|
+
SSML is Google Assistant's markup language for text to speech. It provides options to pause, interpret dates and numbers, and more. You can provide SSML responses or plain text. See [Google's documentation on SSML](https://developers.google.com/actions/reference/ssml).
|
169
|
+
|
170
|
+
### Testing your assistant
|
171
|
+
|
172
|
+
You can use any hosting platform.
|
173
|
+
|
174
|
+
1. [Download the `gactions` CLI](https://developers.google.com/actions/tools/gactions-cli) and add it to your PATH.
|
175
|
+
- Or if you'd rather not put it in your path, you'll simply need to call it by referencing its full path.
|
176
|
+
2. Visit the [Google Cloud Console projects page](https://console.cloud.google.com/project). Create a project and make note of the project ID for configuration and deployment.
|
177
|
+
3. Deploy your app to the web. Heroku is a good choice. See [Heroku's documentation](https://devcenter.heroku.com/articles/getting-started-with-ruby#introduction) for more info on how to do this.
|
178
|
+
4. Add an `action.json` file at the root of your project.
|
179
|
+
|
180
|
+
```json
|
181
|
+
{
|
182
|
+
"versionLabel": "1.0.0",
|
183
|
+
"agentInfo": {
|
184
|
+
"languageCode": "en-US",
|
185
|
+
"projectId": "your-google-project-id",
|
186
|
+
"voiceName": "male_1"
|
187
|
+
},
|
188
|
+
"actions": [
|
189
|
+
{
|
190
|
+
"initialTrigger": {
|
191
|
+
"intent": "assistant.intent.action.MAIN"
|
192
|
+
},
|
193
|
+
"httpExecution": {
|
194
|
+
"url": "https://yourapp.domain.com/path-to-your-assistant"
|
195
|
+
}
|
196
|
+
}
|
197
|
+
]
|
198
|
+
}
|
199
|
+
```
|
200
|
+
|
201
|
+
5. Run the following command from the root directory of your project. The `invocation_name` is what you will use to activate your assistant. For example, "OK Google, talk to my action". Name it something unique.
|
202
|
+
|
203
|
+
```
|
204
|
+
gactions preview -action_package=action.json -invocation_name="my action"
|
205
|
+
```
|
206
|
+
|
207
|
+
- `gactions` will ask to access your account and Google developer project. Follow the onscreen instructions to do so.
|
208
|
+
|
209
|
+
6. Use the [web simulator](https://developers.google.com/actions/tools/web-simulator) to simulate. Or better yet, if your Google Home device is logged into the same account you're using to build your action, you can say "OK Google, talk to my action" to test it out directly on your device.
|
210
|
+
|
211
|
+
## More information
|
212
|
+
|
213
|
+
Check out Google's instructions at https://developers.google.com/actions/develop/sdk/getting-started for more detail on writing and testing a Google Assistant action.
|
214
|
+
|
215
|
+
Check out https://github.com/armilam/google_assistant_example for a simple example of this gem in action.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "google_assistant/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "google_assistant"
|
8
|
+
spec.version = GoogleAssistant::VERSION
|
9
|
+
spec.authors = ["Aaron Milam"]
|
10
|
+
spec.email = ["armilam@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "Ruby SDK for the Google Assistant API"
|
13
|
+
spec.description = "This SDK provides the framework for creating Google Assistant actions in Ruby. It works in Ruby on Rails and Sinatra (and probably others)."
|
14
|
+
spec.homepage = "https://github.com/armilam/google-assistant-ruby"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
21
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
22
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
23
|
+
end
|
data/lib/google_assistant.rb
CHANGED
@@ -2,148 +2,9 @@
|
|
2
2
|
|
3
3
|
Dir["#{File.dirname(__FILE__)}/google_assistant/**/*.rb"].each { |file| require file }
|
4
4
|
|
5
|
-
|
6
|
-
attr_reader :params
|
7
|
-
attr_reader :response
|
5
|
+
module GoogleAssistant
|
8
6
|
|
9
|
-
def
|
10
|
-
|
11
|
-
@response = response
|
12
|
-
end
|
13
|
-
|
14
|
-
def respond_to(&block)
|
15
|
-
yield(self)
|
16
|
-
|
17
|
-
response.headers["Google-Assistant-API-Version"] = "v1"
|
18
|
-
|
19
|
-
intent.call
|
20
|
-
end
|
21
|
-
|
22
|
-
def intent
|
23
|
-
@_intent ||= Intent.new(intent_string)
|
24
|
-
end
|
25
|
-
|
26
|
-
def arguments
|
27
|
-
@_arguments ||= inputs[0]["arguments"].map do |argument|
|
28
|
-
Argument.new(argument)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def conversation
|
33
|
-
@_conversation ||= Conversation.new(conversation_params)
|
34
|
-
end
|
35
|
-
|
36
|
-
def tell(message)
|
37
|
-
final_response = { speech_response: {} }
|
38
|
-
|
39
|
-
if is_ssml(message)
|
40
|
-
final_response[:speech_response][:ssml] = message
|
41
|
-
else
|
42
|
-
final_response[:speech_response][:text_to_speech] = message
|
43
|
-
end
|
44
|
-
|
45
|
-
build_response(nil, false, nil, final_response)
|
46
|
-
end
|
47
|
-
|
48
|
-
def ask(input_prompt)
|
49
|
-
if input_prompt.nil?
|
50
|
-
return handle_error("Invalid input prompt")
|
51
|
-
end
|
52
|
-
|
53
|
-
if input_prompt.is_a?(String)
|
54
|
-
input_prompt = build_input_prompt(is_ssml(input_prompt), input_prompt)
|
55
|
-
end
|
56
|
-
|
57
|
-
expected_intent = build_expected_intent(StandardIntents::TEXT)
|
58
|
-
|
59
|
-
expected_inputs = [{
|
60
|
-
input_prompt: input_prompt,
|
61
|
-
possible_intents: [expected_intent]
|
62
|
-
}]
|
63
|
-
|
64
|
-
build_response(
|
65
|
-
conversation.dialog_state,
|
66
|
-
true,
|
67
|
-
expected_inputs,
|
68
|
-
nil
|
69
|
-
)
|
70
|
-
end
|
71
|
-
|
72
|
-
def build_input_prompt(is_ssml, initial_prompt, no_inputs = [])
|
73
|
-
if is_ssml
|
74
|
-
initial_prompts = [
|
75
|
-
{ ssml: initial_prompt }
|
76
|
-
]
|
77
|
-
|
78
|
-
no_input_prompts = no_inputs.map do |no_input_prompt|
|
79
|
-
{ ssml: no_input_prompt }
|
80
|
-
end
|
81
|
-
|
82
|
-
{
|
83
|
-
initial_prompts: initial_prompts,
|
84
|
-
no_input_prompts: no_input_prompts
|
85
|
-
}
|
86
|
-
else
|
87
|
-
initial_prompts = [
|
88
|
-
{ text_to_speech: initial_prompt }
|
89
|
-
]
|
90
|
-
|
91
|
-
no_input_prompts = no_inputs.map do |no_input_prompt|
|
92
|
-
{ text_to_speech: no_input_prompt }
|
93
|
-
end
|
94
|
-
|
95
|
-
{
|
96
|
-
initial_prompts: initial_prompts,
|
97
|
-
no_input_prompts: no_input_prompts
|
98
|
-
}
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
private
|
103
|
-
|
104
|
-
def build_response(dialog_state, expect_user_response, expected_input, final_response)
|
105
|
-
response = {}
|
106
|
-
|
107
|
-
response[:conversation_token] = dialog_state.to_json if dialog_state
|
108
|
-
response[:expect_user_response] = expect_user_response
|
109
|
-
response[:expected_inputs] = expected_input if expected_input
|
110
|
-
response[:final_response] = final_response if !expect_user_response && final_response
|
111
|
-
|
112
|
-
{
|
113
|
-
json: response
|
114
|
-
}
|
115
|
-
end
|
116
|
-
|
117
|
-
def build_expected_intent(intent)
|
118
|
-
if intent.nil? || intent == ""
|
119
|
-
return handle_error("Invalid intent")
|
120
|
-
end
|
121
|
-
|
122
|
-
{ intent: intent }
|
123
|
-
end
|
124
|
-
|
125
|
-
def is_ssml(text)
|
126
|
-
if text.nil?
|
127
|
-
handle_error("Missing text")
|
128
|
-
return false
|
129
|
-
end
|
130
|
-
|
131
|
-
text =~ /^<speak\b[^>]*>(.*?)<\/speak>$/
|
132
|
-
end
|
133
|
-
|
134
|
-
def inputs
|
135
|
-
params["inputs"] || handle_error("Missing inputs from request body")
|
136
|
-
end
|
137
|
-
|
138
|
-
def intent_string
|
139
|
-
inputs[0]["intent"] || handle_error("Missing intent from request body")
|
140
|
-
end
|
141
|
-
|
142
|
-
def conversation_params
|
143
|
-
params["conversation"] || {}
|
144
|
-
end
|
145
|
-
|
146
|
-
def handle_error(message)
|
147
|
-
raise message
|
7
|
+
def self.respond_to(params, response, &block)
|
8
|
+
Assistant.new(params, response).respond_to(&block)
|
148
9
|
end
|
149
10
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
|
1
|
+
module GoogleAssistant
|
2
2
|
class Argument
|
3
3
|
attr_reader :name, :raw_text, :text_value
|
4
4
|
|
5
5
|
def initialize(opts)
|
6
6
|
@name = opts["name"]
|
7
|
-
@raw_text = opts
|
7
|
+
@raw_text = opts["raw_text"]
|
8
8
|
@text_value = opts["text_value"]
|
9
9
|
end
|
10
10
|
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module GoogleAssistant
|
6
|
+
class Assistant
|
7
|
+
InvalidIntent = Class.new(StandardError)
|
8
|
+
InvalidMessage = Class.new(StandardError)
|
9
|
+
InvalidInputPrompt = Class.new(StandardError)
|
10
|
+
MissingRequestInputs = Class.new(StandardError)
|
11
|
+
MissingRequestIntent = Class.new(StandardError)
|
12
|
+
|
13
|
+
attr_reader :params, :response
|
14
|
+
|
15
|
+
def initialize(params, response)
|
16
|
+
@params = params
|
17
|
+
@response = response
|
18
|
+
end
|
19
|
+
|
20
|
+
def respond_to(&block)
|
21
|
+
yield(self)
|
22
|
+
|
23
|
+
response.headers["Google-Assistant-API-Version"] = "v1"
|
24
|
+
|
25
|
+
intent.call
|
26
|
+
end
|
27
|
+
|
28
|
+
def intent
|
29
|
+
@_intent ||= Intent.new(intent_string)
|
30
|
+
end
|
31
|
+
|
32
|
+
def arguments
|
33
|
+
@_arguments ||= inputs[0]["arguments"].map do |argument|
|
34
|
+
Argument.new(argument)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def conversation
|
39
|
+
@_conversation ||= Conversation.new(conversation_params)
|
40
|
+
end
|
41
|
+
|
42
|
+
def tell(message)
|
43
|
+
raise InvalidMessage if message.nil? || message.empty?
|
44
|
+
|
45
|
+
final_response = { speech_response: {} }
|
46
|
+
|
47
|
+
if is_ssml?(message)
|
48
|
+
final_response[:speech_response][:ssml] = message
|
49
|
+
else
|
50
|
+
final_response[:speech_response][:text_to_speech] = message
|
51
|
+
end
|
52
|
+
|
53
|
+
build_response(nil, false, nil, final_response)
|
54
|
+
end
|
55
|
+
|
56
|
+
def ask(prompt:, no_input_prompt: [])
|
57
|
+
raise InvalidInputPrompt if prompt.nil? || prompt.empty?
|
58
|
+
|
59
|
+
no_input_prompt = [*no_input_prompt].compact
|
60
|
+
|
61
|
+
prompt = build_input_prompt(prompt, no_input_prompt)
|
62
|
+
|
63
|
+
expected_intent = build_expected_intent(StandardIntents::TEXT)
|
64
|
+
|
65
|
+
expected_inputs = [{
|
66
|
+
input_prompt: prompt,
|
67
|
+
possible_intents: [expected_intent]
|
68
|
+
}]
|
69
|
+
|
70
|
+
build_response(
|
71
|
+
conversation.dialog_state,
|
72
|
+
true,
|
73
|
+
expected_inputs,
|
74
|
+
nil
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def build_input_prompt(initial_prompt, no_inputs = [])
|
81
|
+
initial_prompts = [
|
82
|
+
{ prompt_type(initial_prompt) => initial_prompt }
|
83
|
+
]
|
84
|
+
|
85
|
+
no_input_prompts = no_inputs.map do |prompt|
|
86
|
+
{ prompt_type(prompt) => prompt }
|
87
|
+
end
|
88
|
+
|
89
|
+
{
|
90
|
+
initial_prompts: initial_prompts,
|
91
|
+
no_input_prompts: no_input_prompts
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def build_response(dialog_state, expect_user_response, expected_input, final_response)
|
96
|
+
response = {}
|
97
|
+
|
98
|
+
response[:conversation_token] = dialog_state.to_json if dialog_state
|
99
|
+
response[:expect_user_response] = expect_user_response
|
100
|
+
response[:expected_inputs] = expected_input if expected_input
|
101
|
+
response[:final_response] = final_response if !expect_user_response && final_response
|
102
|
+
|
103
|
+
response
|
104
|
+
end
|
105
|
+
|
106
|
+
def build_expected_intent(intent)
|
107
|
+
raise InvalidIntent if intent.nil? || intent.empty?
|
108
|
+
|
109
|
+
{ intent: intent }
|
110
|
+
end
|
111
|
+
|
112
|
+
def is_ssml?(text)
|
113
|
+
text =~ /^<speak\b[^>]*>(.*?)<\/speak>$/
|
114
|
+
end
|
115
|
+
|
116
|
+
def prompt_type(text)
|
117
|
+
is_ssml?(text) ? :ssml : :text_to_speech
|
118
|
+
end
|
119
|
+
|
120
|
+
def inputs
|
121
|
+
raise MissingRequestInputs if params["inputs"].nil?
|
122
|
+
params["inputs"]
|
123
|
+
end
|
124
|
+
|
125
|
+
def intent_string
|
126
|
+
raise MissingRequestIntent if inputs[0]["intent"].nil?
|
127
|
+
inputs[0]["intent"]
|
128
|
+
end
|
129
|
+
|
130
|
+
def conversation_params
|
131
|
+
params["conversation"] || {}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module GoogleAssistant
|
6
|
+
class DialogState
|
7
|
+
DEFAULT_STATE = { "state" => nil, "data" => {} }.freeze
|
8
|
+
|
9
|
+
def initialize(state_hash_or_conversation_token = nil)
|
10
|
+
if state_hash_or_conversation_token.is_a?(String)
|
11
|
+
@raw_token = state_hash_or_conversation_token
|
12
|
+
@state_hash = parse_token(state_hash_or_conversation_token)
|
13
|
+
elsif state_hash_or_conversation_token.is_a?(Hash)
|
14
|
+
@state_hash = state_hash_or_conversation_token
|
15
|
+
else
|
16
|
+
@state_hash = DEFAULT_STATE.dup
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def state
|
21
|
+
state_hash["state"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def state=(state)
|
25
|
+
state_hash["state"] = state
|
26
|
+
end
|
27
|
+
|
28
|
+
def data
|
29
|
+
state_hash["data"]
|
30
|
+
end
|
31
|
+
|
32
|
+
def data=(data)
|
33
|
+
raise "DialogState data must be a hash" unless data.is_a?(Hash)
|
34
|
+
|
35
|
+
state_hash["data"] = data
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_json
|
39
|
+
state_hash.to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :state_hash, :raw_token
|
45
|
+
|
46
|
+
def parse_token(token)
|
47
|
+
JSON.parse(token)
|
48
|
+
rescue JSON::ParserError, TypeError
|
49
|
+
DEFAULT_STATE.dup
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
metadata
CHANGED
@@ -1,25 +1,81 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: google_assistant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Milam
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
13
|
-
|
14
|
-
|
11
|
+
date: 2017-01-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '12.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '12.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
description: This SDK provides the framework for creating Google Assistant actions
|
56
|
+
in Ruby. It works in Ruby on Rails and Sinatra (and probably others).
|
57
|
+
email:
|
58
|
+
- armilam@gmail.com
|
15
59
|
executables: []
|
16
60
|
extensions: []
|
17
61
|
extra_rdoc_files: []
|
18
62
|
files:
|
63
|
+
- ".editorconfig"
|
64
|
+
- ".gitignore"
|
65
|
+
- ".ruby-version"
|
66
|
+
- Gemfile
|
67
|
+
- Gemfile.lock
|
68
|
+
- LICENSE.md
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- google_assistant.gemspec
|
19
72
|
- lib/google_assistant.rb
|
20
73
|
- lib/google_assistant/argument.rb
|
74
|
+
- lib/google_assistant/assistant.rb
|
21
75
|
- lib/google_assistant/conversation.rb
|
76
|
+
- lib/google_assistant/dialog_state.rb
|
22
77
|
- lib/google_assistant/intent.rb
|
78
|
+
- lib/google_assistant/version.rb
|
23
79
|
homepage: https://github.com/armilam/google-assistant-ruby
|
24
80
|
licenses:
|
25
81
|
- MIT
|