adhearsion-asr 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +3 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +6 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +20 -0
- data/README.md +172 -0
- data/Rakefile +6 -0
- data/adhearsion_asr.gemspec +28 -0
- data/lib/adhearsion-asr/ask_grammar_builder.rb +38 -0
- data/lib/adhearsion-asr/controller_methods.rb +112 -0
- data/lib/adhearsion-asr/menu_builder.rb +122 -0
- data/lib/adhearsion-asr/plugin.rb +11 -0
- data/lib/adhearsion-asr/prompt_builder.rb +54 -0
- data/lib/adhearsion-asr/result.rb +11 -0
- data/lib/adhearsion-asr/version.rb +3 -0
- data/lib/adhearsion-asr.rb +7 -0
- data/spec/adhearsion-asr/controller_methods_spec.rb +989 -0
- data/spec/spec_helper.rb +13 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c04b1a33ab083f07c27fcb249b183b1261fd24ff
|
4
|
+
data.tar.gz: 30b74b8f7774084d0ba658b207330ba6070ea19e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 812e7c4fceaed9eccf44d9443ad42341b9f4c7bcc7b7b7a35b21e68813c316b7427de465fe01a42dbbc98474853bf0c9ef65e502a21c526e62b0a1d7c77cb3e9
|
7
|
+
data.tar.gz: ca1aff66a38dc817b8929ed2229323736aedcb86c2d32778e8ed7832b0d5fb4baf31b2bb2a74a92b288df40d77dca35a77f9b04af9580aec5cde016648e7b6f6
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# [develop](https://github.com/adhearsion/adhearsion-asr)
|
2
|
+
|
3
|
+
# [v0.1.0](https://github.com/adhearsion/adhearsion-asr/compare/6216ddb0a8b8c0ac5d1731ec154fe6d6abfea692...0.1.0) - [2013-05-07](https://rubygems.org/gems/adhearsion-asr/versions/0.1.0)
|
4
|
+
* Feature: #ask and #menu from Adhearsion core
|
5
|
+
* Mostly API compatible, with some very minor differences
|
6
|
+
* Use Rayo Prompt component
|
7
|
+
* Support for arbitrary grammars (ASR) in #ask
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (C) 2013 Adhearsion Foundation Inc
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
# adhearsion-asr
|
2
|
+
|
3
|
+
Adds speech recognition support to Adhearsion as a plugin. Overrides `CallController#ask` and `#menu` to pass all recognition responsibility to the recognizer instead of invoking it multiple times.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
* #ask API from Adhearsion core supporting digit limits, terminators, timeouts, inline grammars and grammar references
|
8
|
+
* #menu API from Adhearsion core supporting DTMF input and recognition failure
|
9
|
+
|
10
|
+
## Install
|
11
|
+
|
12
|
+
Add the following entries to your Adhearsion application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'adhearsion-asr'
|
16
|
+
gem 'adhearsion', github: 'adhearsion', branch: 'feature/rayo_prompt'
|
17
|
+
gem 'punchblock', github: 'adhearsion/punchblock', branch: 'feature/new_rayo'
|
18
|
+
```
|
19
|
+
|
20
|
+
The dependencies on Adhearsion and Punchblock from git are temporary and are required because this plugin uses functionality that is unreleased in those gems (mostly changes to keep up to date with the Rayo specification; they will be released once the Rayo specification has been advanced to Draft by the XSF).
|
21
|
+
|
22
|
+
Be sure to check out the plugin config by running `rake config:show` and adjust to your requirements.
|
23
|
+
|
24
|
+
## Examples
|
25
|
+
|
26
|
+
### Simple collection of 5 DTMF digits
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
class MyController < Adhearsion::CallController
|
30
|
+
include AdhearsionASR::ControllerMethods
|
31
|
+
|
32
|
+
def run
|
33
|
+
result = ask limit: 5
|
34
|
+
case result.status
|
35
|
+
when :match
|
36
|
+
speak "You entered #{result.response}"
|
37
|
+
when :noinput
|
38
|
+
speak "Hellooo? Anyone there?"
|
39
|
+
when :nomatch
|
40
|
+
speak "That doesn't make sense."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
### Collecting an arbitrary number of digits until '#' is received:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class MyController < Adhearsion::CallController
|
50
|
+
include AdhearsionASR::ControllerMethods
|
51
|
+
|
52
|
+
def run
|
53
|
+
result = ask terminator: '#'
|
54
|
+
case result.status
|
55
|
+
when :match
|
56
|
+
speak "You entered #{result.response}"
|
57
|
+
when :noinput
|
58
|
+
speak "Hellooo? Anyone there?"
|
59
|
+
when :nomatch
|
60
|
+
speak "That doesn't make sense."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
### Collecting input from an inline speech grammar
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
class MyController < Adhearsion::CallController
|
70
|
+
include AdhearsionASR::ControllerMethods
|
71
|
+
|
72
|
+
def run
|
73
|
+
grammar = RubySpeech::GRXML.draw root: 'main', language: 'en-us', mode: :voice do
|
74
|
+
rule id: 'main', scope: 'public' do
|
75
|
+
one_of do
|
76
|
+
item { 'yes' }
|
77
|
+
item { 'no' }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
result = ask grammar: grammar, input_options: { mode: :speech }
|
83
|
+
case result.status
|
84
|
+
when :match
|
85
|
+
speak "You said #{result.response}"
|
86
|
+
when :noinput
|
87
|
+
speak "Hellooo? Anyone there?"
|
88
|
+
when :nomatch
|
89
|
+
speak "That doesn't make sense."
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
### Collecting input from a speech grammar by URL
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
class MyController < Adhearsion::CallController
|
99
|
+
include AdhearsionASR::ControllerMethods
|
100
|
+
|
101
|
+
def run
|
102
|
+
result = ask grammar_url: 'http://example.com/mygrammar.grxml', input_options: { mode: :speech }
|
103
|
+
case result.status
|
104
|
+
when :match
|
105
|
+
speak "You said #{result.response}"
|
106
|
+
when :noinput
|
107
|
+
speak "Hellooo? Anyone there?"
|
108
|
+
when :nomatch
|
109
|
+
speak "That doesn't make sense."
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
### Executing a DTMF menu
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
class MyController < Adhearsion::CallController
|
119
|
+
def run
|
120
|
+
answer
|
121
|
+
menu "Where can we take you today?", timeout: 8.seconds, tries: 3 do
|
122
|
+
match 1, BooController
|
123
|
+
match '2', MyOtherController
|
124
|
+
match(3, 4) { pass YetAnotherController }
|
125
|
+
match 5, FooController
|
126
|
+
match 6..10 do |dialed|
|
127
|
+
say_dialed dialed
|
128
|
+
end
|
129
|
+
|
130
|
+
timeout { do_this_on_timeout }
|
131
|
+
|
132
|
+
invalid do
|
133
|
+
invoke InvalidController
|
134
|
+
end
|
135
|
+
|
136
|
+
failure do
|
137
|
+
speak 'Goodbye'
|
138
|
+
hangup
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
speak "This code gets executed unless #pass is used"
|
143
|
+
end
|
144
|
+
|
145
|
+
def say_dialed(dialed)
|
146
|
+
speak "#{dialed} was dialed"
|
147
|
+
end
|
148
|
+
|
149
|
+
def do_this_on_timeout
|
150
|
+
speak 'Timeout'
|
151
|
+
end
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
Check out the [API documentation](http://rdoc.info/gems/adhearsion-asr/frames) for more details.
|
156
|
+
|
157
|
+
## Links:
|
158
|
+
* [Source](https://github.com/adhearsion/adhearsion-asr)
|
159
|
+
* [Documentation](http://rdoc.info/gems/adhearsion-asr/frames)
|
160
|
+
* [Bug Tracker](https://github.com/adhearsion/adhearsion-asr/issues)
|
161
|
+
|
162
|
+
## Author
|
163
|
+
|
164
|
+
[Ben Langfeld](https://github.com/benlangfeld)
|
165
|
+
|
166
|
+
### Contributions
|
167
|
+
|
168
|
+
Adhearsion has a set of [contribution guidelines](https://github.com/adhearsion/adhearsion/wiki/Contributing) which help to smooth the contribution process.
|
169
|
+
|
170
|
+
### Copyright
|
171
|
+
|
172
|
+
Copyright (c) 2013 Adhearsion Foundation Inc. MIT LICENSE (see LICENSE for details).
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "adhearsion-asr/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "adhearsion-asr"
|
7
|
+
s.version = AdhearsionASR::VERSION
|
8
|
+
s.authors = ["Ben Langfeld"]
|
9
|
+
s.email = ["ben@langfeld.me"]
|
10
|
+
s.homepage = "https://github.com/adhearsion/adhearsion-asr"
|
11
|
+
s.summary = %q{Adds speech recognition support to Adhearsion as a plugin}
|
12
|
+
s.description = %q{Adds speech recognition support to Adhearsion as a plugin}
|
13
|
+
|
14
|
+
s.rubyforge_project = "adhearsion-asr"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_runtime_dependency %q<adhearsion>, ["~> 2.1"]
|
21
|
+
s.add_runtime_dependency %q<ruby_speech>, ["~> 2.1"]
|
22
|
+
|
23
|
+
s.add_development_dependency %q<bundler>, ["~> 1.0"]
|
24
|
+
s.add_development_dependency %q<rspec>, ["~> 2.5"]
|
25
|
+
s.add_development_dependency %q<rake>, [">= 0"]
|
26
|
+
s.add_development_dependency %q<guard-rspec>
|
27
|
+
s.add_development_dependency %q<rb-fsevent>, ['~> 0.9']
|
28
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module AdhearsionASR
|
2
|
+
class AskGrammarBuilder
|
3
|
+
def initialize(options)
|
4
|
+
@options = options
|
5
|
+
end
|
6
|
+
|
7
|
+
def grammars
|
8
|
+
@grammars ||= build_grammars
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def build_grammars
|
14
|
+
grammars = []
|
15
|
+
|
16
|
+
grammars.concat [@options[:grammar]].flatten.compact.map { |val| {value: val} } if @options[:grammar]
|
17
|
+
grammars.concat [@options[:grammar_url]].flatten.compact.map { |val| {url: val} } if @options[:grammar_url]
|
18
|
+
|
19
|
+
if grammars.empty?
|
20
|
+
limit = @options[:limit]
|
21
|
+
grammar = RubySpeech::GRXML.draw mode: :dtmf, root: 'digits' do
|
22
|
+
rule id: 'digits', scope: 'public' do
|
23
|
+
item repeat: "0-#{limit}" do
|
24
|
+
one_of do
|
25
|
+
0.upto(9) { |d| item { d.to_s } }
|
26
|
+
item { "#" }
|
27
|
+
item { "*" }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
grammars << {value: grammar}
|
33
|
+
end
|
34
|
+
|
35
|
+
grammars
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'adhearsion-asr/ask_grammar_builder'
|
2
|
+
require 'adhearsion-asr/prompt_builder'
|
3
|
+
require 'adhearsion-asr/menu_builder'
|
4
|
+
|
5
|
+
module AdhearsionASR
|
6
|
+
module ControllerMethods
|
7
|
+
|
8
|
+
#
|
9
|
+
# Prompts for input, handling playback of prompts, DTMF grammar construction, and execution
|
10
|
+
#
|
11
|
+
# @example A basic DTMF digit collection:
|
12
|
+
# ask "Welcome, ", "/opt/sounds/menu-prompt.mp3",
|
13
|
+
# timeout: 10, terminator: '#', limit: 3
|
14
|
+
#
|
15
|
+
# The first arguments will be a list of sounds to play, as accepted by #play, including strings for TTS, Date and Time objects, and file paths.
|
16
|
+
# :timeout, :terminator and :limit options may be specified to automatically construct a grammar, or grammars may be manually specified.
|
17
|
+
#
|
18
|
+
# @param [Object, Array<Object>] args A list of outputs to play, as accepted by #play
|
19
|
+
# @param [Hash] options Options to modify the grammar
|
20
|
+
# @option options [Boolean] :interruptible If the prompt should be interruptible or not. Defaults to true
|
21
|
+
# @option options [Integer] :limit Digit limit (causes collection to cease after a specified number of digits have been collected)
|
22
|
+
# @option options [Integer] :timeout Timeout in seconds before the first and between each input digit
|
23
|
+
# @option options [String] :terminator Digit to terminate input
|
24
|
+
# @option options [RubySpeech::GRXML::Grammar, Array<RubySpeech::GRXML::Grammar>] :grammar One of a collection of grammars to execute
|
25
|
+
# @option options [String, Array<String>] :grammar_url One of a collection of URLs for grammars to execute
|
26
|
+
# @option options [Hash] :input_options A hash of options passed directly to the Punchblock Input constructor
|
27
|
+
# @option options [Hash] :output_options A hash of options passed directly to the Punchblock Output constructor
|
28
|
+
#
|
29
|
+
# @return [Result] a result object from which the details of the response may be established
|
30
|
+
#
|
31
|
+
# @see Output#play
|
32
|
+
# @see Punchblock::Component::Input.new
|
33
|
+
# @see Punchblock::Component::Output.new
|
34
|
+
#
|
35
|
+
def ask(*args)
|
36
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
37
|
+
prompts = args.flatten
|
38
|
+
|
39
|
+
options[:grammar] || options[:grammar_url] || options[:limit] || options[:terminator] || raise(ArgumentError, "You must specify at least one of limit, terminator or grammar")
|
40
|
+
|
41
|
+
output_document = output_formatter.ssml_for_collection(prompts)
|
42
|
+
grammars = AskGrammarBuilder.new(options).grammars
|
43
|
+
|
44
|
+
PromptBuilder.new(output_document, grammars, options).execute self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Creates and manages a multiple choice menu driven by DTMF, handling playback of prompts,
|
48
|
+
# invalid input, retries and timeouts, and final failures.
|
49
|
+
#
|
50
|
+
# @example A complete example of the method is as follows:
|
51
|
+
# menu "Welcome, ", "/opt/sounds/menu-prompt.mp3", tries: 2, timeout: 10 do
|
52
|
+
# match 1, OperatorController
|
53
|
+
#
|
54
|
+
# match 10..19 do
|
55
|
+
# pass DirectController
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# match 5, 6, 9 do |exten|
|
59
|
+
# play "The #{exten} extension is currently not active"
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# match '7', OfficeController
|
63
|
+
#
|
64
|
+
# invalid { play "Please choose a valid extension" }
|
65
|
+
# timeout { play "Input timed out, try again." }
|
66
|
+
# failure { pass OperatorController }
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# The first arguments will be a list of sounds to play, as accepted by #play, including strings for TTS, Date and Time objects, and file paths.
|
70
|
+
# :tries and :timeout options respectively specify the number of tries before going into failure, and the timeout in seconds allowed on each digit input.
|
71
|
+
# The most important part is the following block, which specifies how the menu will be constructed and handled.
|
72
|
+
#
|
73
|
+
# #match handles connecting an input pattern to a payload.
|
74
|
+
# The pattern can be one or more of: an integer, a Range, a string, an Array of the possible single types.
|
75
|
+
# Input is matched against patterns, and the first exact match has it's payload executed.
|
76
|
+
# Matched input is passed in to the associated block, or to the controller through #options.
|
77
|
+
#
|
78
|
+
# Allowed payloads are the name of a controller class, in which case it is executed through its #run method, or a block, which is executed in the context of the current controller.
|
79
|
+
#
|
80
|
+
# #invalid has its associated block executed when the input does not possibly match any pattern.
|
81
|
+
# #timeout's block is run when timeout expires before receiving any input
|
82
|
+
# #failure runs its block when the maximum number of tries is reached without an input match.
|
83
|
+
#
|
84
|
+
# Execution of the current context resumes after #menu finishes. If you wish to jump to an entirely different controller, use #pass.
|
85
|
+
# Menu will return :failed if failure was reached, or :done if a match was executed.
|
86
|
+
#
|
87
|
+
# @param [Object] args A list of outputs to play, as accepted by #play
|
88
|
+
# @param [Hash] options Options to use for the menu
|
89
|
+
# @option options [Integer] :tries Number of tries allowed before failure
|
90
|
+
# @option options [Integer] :timeout Timeout in seconds before the first and between each input digit
|
91
|
+
# @option options [Boolean] :interruptible If the prompt should be interruptible or not. Defaults to true
|
92
|
+
# @option options [Hash] :input_options A hash of options passed directly to the Punchblock Input constructor
|
93
|
+
# @option options [Hash] :output_options A hash of options passed directly to the Punchblock Output constructor
|
94
|
+
#
|
95
|
+
# @return [Result] a result object from which the details of the response may be established
|
96
|
+
#
|
97
|
+
# @see Output#play
|
98
|
+
# @see CallController#pass
|
99
|
+
#
|
100
|
+
def menu(*args, &block)
|
101
|
+
raise ArgumentError, "You must specify a block to build the menu" unless block
|
102
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
103
|
+
prompts = args.flatten
|
104
|
+
|
105
|
+
menu_builder = MenuBuilder.new(options, &block)
|
106
|
+
|
107
|
+
output_document = output_formatter.ssml_for_collection(prompts)
|
108
|
+
|
109
|
+
menu_builder.execute output_document, self
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'adhearsion-asr/prompt_builder'
|
2
|
+
|
3
|
+
module AdhearsionASR
|
4
|
+
class MenuBuilder
|
5
|
+
def initialize(options, &block)
|
6
|
+
@options = options
|
7
|
+
@matchers = []
|
8
|
+
@callbacks = {}
|
9
|
+
build(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def match(*args, &block)
|
13
|
+
payload = block || args.pop
|
14
|
+
|
15
|
+
@matchers << Matcher.new(payload, args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def invalid(&block)
|
19
|
+
register_user_supplied_callback :nomatch, &block
|
20
|
+
end
|
21
|
+
|
22
|
+
def timeout(&block)
|
23
|
+
register_user_supplied_callback :noinput, &block
|
24
|
+
end
|
25
|
+
|
26
|
+
def failure(&block)
|
27
|
+
register_user_supplied_callback :failure, &block
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute(output_document, controller)
|
31
|
+
catch :match do
|
32
|
+
(@options[:tries] || 1).times do
|
33
|
+
result = PromptBuilder.new(output_document, grammars, @options).execute(controller)
|
34
|
+
process_result result
|
35
|
+
end
|
36
|
+
execute_hook :failure
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def grammars
|
43
|
+
@grammar ||= [{value: build_grammar}]
|
44
|
+
end
|
45
|
+
|
46
|
+
def process_result(result)
|
47
|
+
if result.status == :match
|
48
|
+
handle_match result
|
49
|
+
else
|
50
|
+
execute_hook result.status
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def register_user_supplied_callback(name, &block)
|
55
|
+
@callbacks[name] = block
|
56
|
+
end
|
57
|
+
|
58
|
+
def execute_hook(hook_name)
|
59
|
+
callback = @callbacks[hook_name]
|
60
|
+
return unless callback
|
61
|
+
@context.instance_exec(&callback)
|
62
|
+
end
|
63
|
+
|
64
|
+
def handle_match(result)
|
65
|
+
match = @matchers[result.interpretation.to_i]
|
66
|
+
match.dispatch @context, result.response
|
67
|
+
throw :match
|
68
|
+
end
|
69
|
+
|
70
|
+
def build(&block)
|
71
|
+
@context = eval "self", block.binding
|
72
|
+
instance_eval(&block)
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_grammar
|
76
|
+
raise ArgumentError, "You must specify one or more matches." if @matchers.count < 1
|
77
|
+
matchers = @matchers
|
78
|
+
|
79
|
+
RubySpeech::GRXML.draw mode: :dtmf, root: 'options', tag_format: 'semantics/1.0-literals' do
|
80
|
+
rule id: 'options', scope: 'public' do
|
81
|
+
item do
|
82
|
+
one_of do
|
83
|
+
matchers.each_with_index do |matcher, index|
|
84
|
+
item do
|
85
|
+
tag { index.to_s }
|
86
|
+
matcher.apply_to_grammar self
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
Matcher = Struct.new(:payload, :keys) do
|
96
|
+
def dispatch(controller, response)
|
97
|
+
if payload.is_a?(Proc)
|
98
|
+
controller.instance_exec response, &payload
|
99
|
+
else
|
100
|
+
controller.invoke payload, extension: response
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def apply_to_grammar(grammar)
|
105
|
+
possible_options = calculate_possible_options
|
106
|
+
if possible_options.count > 1
|
107
|
+
grammar.one_of do
|
108
|
+
possible_options.each do |key|
|
109
|
+
item { key.to_s }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
else
|
113
|
+
keys.first.to_s
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def calculate_possible_options
|
118
|
+
keys.map { |key| key.respond_to?(:to_a) ? key.to_a : key }.flatten
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module AdhearsionASR
|
2
|
+
class Plugin < Adhearsion::Plugin
|
3
|
+
config :adhearsion_asr do
|
4
|
+
min_confidence 0.5, desc: 'The default minimum confidence level used for all recognizer invocations.', transform: Proc.new { |v| v.to_f }
|
5
|
+
timeout 5, desc: 'The default timeout (in seconds) used for all recognizer invocations.', transform: Proc.new { |v| v.to_i }
|
6
|
+
recognizer nil, desc: 'The default recognizer used for all input. Set nil to use platform default.'
|
7
|
+
input_language 'en-US', desc: 'The default language set on generated grammars. Set nil to use platform default.'
|
8
|
+
renderer nil, desc: 'The default renderer used for all output. Set nil to use platform default.'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'adhearsion-asr/result'
|
2
|
+
|
3
|
+
module AdhearsionASR
|
4
|
+
class PromptBuilder
|
5
|
+
def initialize(output_document, grammars, options)
|
6
|
+
output_options = {
|
7
|
+
render_document: {value: output_document},
|
8
|
+
renderer: Plugin.config.renderer
|
9
|
+
}.merge(options[:output_options] || {})
|
10
|
+
|
11
|
+
input_options = {
|
12
|
+
mode: :dtmf,
|
13
|
+
initial_timeout: (options[:timeout] || Plugin.config.timeout) * 1000,
|
14
|
+
inter_digit_timeout: (options[:timeout] || Plugin.config.timeout) * 1000,
|
15
|
+
max_silence: (options[:timeout] || Plugin.config.timeout) * 1000,
|
16
|
+
min_confidence: Plugin.config.min_confidence,
|
17
|
+
grammars: grammars,
|
18
|
+
recognizer: Plugin.config.recognizer,
|
19
|
+
language: Plugin.config.input_language,
|
20
|
+
terminator: options[:terminator]
|
21
|
+
}.merge(options[:input_options] || {})
|
22
|
+
|
23
|
+
@prompt = Punchblock::Component::Prompt.new output_options, input_options, barge_in: options.has_key?(:interruptible) ? options[:interruptible] : true
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute(controller)
|
27
|
+
controller.execute_component_and_await_completion @prompt
|
28
|
+
|
29
|
+
result @prompt.complete_event.reason
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def result(reason)
|
35
|
+
Result.new.tap do |result|
|
36
|
+
case reason
|
37
|
+
when proc { |r| r.respond_to? :nlsml }
|
38
|
+
result.status = :match
|
39
|
+
result.confidence = reason.confidence
|
40
|
+
result.response = reason.utterance
|
41
|
+
result.interpretation = reason.interpretation
|
42
|
+
result.nlsml = reason.nlsml
|
43
|
+
when Punchblock::Event::Complete::Error
|
44
|
+
raise Error, reason.details
|
45
|
+
when Punchblock::Event::Complete::Reason
|
46
|
+
result.status = reason.name
|
47
|
+
else
|
48
|
+
raise "Unknown completion reason received: #{reason}"
|
49
|
+
end
|
50
|
+
logger.debug "Ask completed with result #{result.inspect}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module AdhearsionASR
|
2
|
+
Result = Struct.new(:status, :confidence, :response, :interpretation, :nlsml) do
|
3
|
+
def to_s
|
4
|
+
response
|
5
|
+
end
|
6
|
+
|
7
|
+
def inspect
|
8
|
+
"#<#{self.class} status=#{status.inspect}, confidence=#{confidence.inspect}, response=#{response.inspect}, interpretation=#{interpretation.inspect}, nlsml=#{nlsml.inspect}>"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|