aws-lex-conversation 0.5.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 196357c0326b8a475650f1be193292f99818ca79b98a9957234f01b5e228caf9
4
- data.tar.gz: 4acba1d9e6647752ecb57d6fed79746214fbf5dbaba2f495085a77ebb0aa2cde
3
+ metadata.gz: bb6d1313f30041f9896bccc30fe2511d7eb0f377f505dc3c3c8e153e2838151d
4
+ data.tar.gz: aa0804ca414e0c68d982ae8ba601a7f46a729678130babb56d7eb87edf119cd3
5
5
  SHA512:
6
- metadata.gz: b66d860466c3514f7e4122dccc9545eaf98b3d0fd8a6535873f323c73366071197c6b545f67eef71d25f86ea1d4ddd02a334660087a7ec3c84e85dce00f9f82d
7
- data.tar.gz: d02d3473ef14600f09d78a94bf839c80dce492943956e1afe565261eca22b33dac50c1d6699687e57d97c69e95955a81a415cc122d122f05aab9393e662bd3a1
6
+ metadata.gz: 3471ce6eddb122e71cb81aad093e663165bae469b8317c045eddb4f3ba21412ed3815e3f9e895aea121a6af61ae143abb15c0cff1520c10243bfe51a807b09b8
7
+ data.tar.gz: 906124b34ad1218de0c2725942620fc530120b855cf8e12552c8fc9b841fa18894a7327d92e908ff02e8e3bf276da7d1c4a951efcb87bb2c8f4dd19ecf057bc0
data/README.md CHANGED
@@ -58,13 +58,13 @@ The first handler that returns `true` for the `will_respond?` method will provid
58
58
  ```ruby
59
59
  class SayHello < Aws::Lex::Conversation::Handler::Base
60
60
  def will_respond?(conversation)
61
- conversation.lex.incovation_source.dialog_code_hook? && # callback is for DialogCodeHook (i.e. validation)
61
+ conversation.lex.invocation_source.dialog_code_hook? && # callback is for DialogCodeHook (i.e. validation)
62
62
  conversation.lex.current_intent.name == 'SayHello' && # Lex has routed to the 'SayHello' intent
63
- conversation.slots[:name] # our expected slot value is set
63
+ conversation.slots[:name].filled? # our expected slot value is set
64
64
  end
65
65
 
66
66
  def response(conversation)
67
- name = conversation.slots[:name]
67
+ name = conversation.slots[:name].value
68
68
 
69
69
  # NOTE: you can use the Type::* classes if you wish. The final output
70
70
  # will be normalized to a value that complies with the Lex response format.
@@ -5,7 +5,7 @@ require_relative 'conversation/base'
5
5
  module Aws
6
6
  module Lex
7
7
  class Conversation
8
- include Support::Responses
8
+ include Support::Mixins::Responses
9
9
 
10
10
  attr_accessor :event, :context, :lex
11
11
 
@@ -10,8 +10,11 @@ require_relative 'response/confirm_intent'
10
10
  require_relative 'response/delegate'
11
11
  require_relative 'response/elicit_intent'
12
12
  require_relative 'response/elicit_slot'
13
- require_relative 'support/responses'
13
+ require_relative 'support/mixins/responses'
14
+ require_relative 'support/mixins/slot_elicitation'
14
15
  require_relative 'support/inflector'
16
+ require_relative 'slot/elicitation'
17
+ require_relative 'slot/elicitor'
15
18
  require_relative 'type/base'
16
19
  require_relative 'type/enumeration'
17
20
  require_relative 'type/sentiment_label'
@@ -22,6 +25,7 @@ require_relative 'type/dialog_action_type'
22
25
  require_relative 'type/confirmation_status'
23
26
  require_relative 'type/fulfillment_state'
24
27
  require_relative 'type/recent_intent_summary_view'
28
+ require_relative 'type/slot'
25
29
  require_relative 'type/slot_resolution'
26
30
  require_relative 'type/slot_detail'
27
31
  require_relative 'type/current_intent'
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Slot
7
+ class Elicitation
8
+ attr_accessor :name, :elicit, :message, :follow_up_message,
9
+ :content_type, :fallback, :maximum_elicitations,
10
+ :conversation
11
+
12
+ def initialize(opts = {})
13
+ self.name = opts.fetch(:name)
14
+ self.elicit = opts.fetch(:elicit) { ->(_c) { true } }
15
+ self.message = opts.fetch(:message)
16
+ self.follow_up_message = opts.fetch(:follow_up_message) { opts.fetch(:message) }
17
+ self.content_type = opts.fetch(:content_type) do
18
+ Aws::Lex::Conversation::Type::Message::ContentType.new('PlainText')
19
+ end
20
+ self.fallback = opts.fetch(:fallback) { ->(_c) {} }
21
+ self.maximum_elicitations = opts.fetch(:maximum_elicitations) { 0 }
22
+ end
23
+
24
+ def elicit!
25
+ return fallback.call(conversation) if maximum_elicitations?
26
+
27
+ increment_slot_elicitations!
28
+ conversation.elicit_slot(
29
+ slot_to_elicit: name,
30
+ message: {
31
+ contentType: content_type,
32
+ content: elicitation_content
33
+ }
34
+ )
35
+ end
36
+
37
+ def elicit?
38
+ elicit.call(conversation) && !slot.filled?
39
+ end
40
+
41
+ private
42
+
43
+ def slot
44
+ conversation.slots[name.to_sym]
45
+ end
46
+
47
+ def elicitation_content
48
+ first_elicitation? ? compose_message(message) : compose_message(follow_up_message)
49
+ end
50
+
51
+ def compose_message(msg)
52
+ msg.is_a?(Proc) ? msg.call(conversation) : msg
53
+ end
54
+
55
+ def increment_slot_elicitations!
56
+ conversation.session[session_key] = elicitation_attempts + 1
57
+ end
58
+
59
+ def maximum_elicitations?
60
+ return false if maximum_elicitations.zero?
61
+
62
+ elicitation_attempts > maximum_elicitations
63
+ end
64
+
65
+ def first_elicitation?
66
+ elicitation_attempts == 1
67
+ end
68
+
69
+ def session_key
70
+ :"SlotElicitations_#{name}"
71
+ end
72
+
73
+ def elicitation_attempts
74
+ conversation.session[session_key].to_i
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Slot
7
+ class Elicitor
8
+ attr_accessor :conversation, :elicitations
9
+
10
+ def initialize(opts = {})
11
+ self.conversation = opts.fetch(:conversation)
12
+ self.elicitations = opts.fetch(:elicitations) { [] }
13
+ elicitations.each do |elicitation|
14
+ elicitation.conversation = conversation
15
+ end
16
+ end
17
+
18
+ def elicit?
19
+ incomplete_elicitations.any?
20
+ end
21
+
22
+ def elicit!
23
+ incomplete_elicitations.first.elicit! if elicit?
24
+ end
25
+
26
+ private
27
+
28
+ def incomplete_elicitations
29
+ elicitations.select(&:elicit?)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Support
7
+ module Mixins
8
+ module Responses
9
+ def close(opts = {})
10
+ params = {
11
+ session_attributes: lex.session_attributes,
12
+ recent_intent_summary_view: lex.recent_intent_summary_view
13
+ }.merge(opts)
14
+ Response::Close.new(params).to_lex
15
+ end
16
+
17
+ def confirm_intent(opts = {})
18
+ params = {
19
+ session_attributes: lex.session_attributes,
20
+ recent_intent_summary_view: lex.recent_intent_summary_view,
21
+ intent_name: lex.current_intent.name,
22
+ slots: lex.current_intent.slots
23
+ }.merge(opts)
24
+ Response::ConfirmIntent.new(params).to_lex
25
+ end
26
+
27
+ def delegate(opts = {})
28
+ params = {
29
+ session_attributes: lex.session_attributes,
30
+ recent_intent_summary_view: lex.recent_intent_summary_view,
31
+ slots: lex.current_intent.slots
32
+ }.merge(opts)
33
+ Response::Delegate.new(params).to_lex
34
+ end
35
+
36
+ def elicit_intent(opts = {})
37
+ params = {
38
+ session_attributes: lex.session_attributes,
39
+ recent_intent_summary_view: lex.recent_intent_summary_view
40
+ }.merge(opts)
41
+ Response::ElicitIntent.new(params).to_lex
42
+ end
43
+
44
+ def elicit_slot(opts = {})
45
+ params = {
46
+ session_attributes: lex.session_attributes,
47
+ recent_intent_summary_view: lex.recent_intent_summary_view,
48
+ slots: lex.current_intent.slots,
49
+ intent_name: lex.current_intent.name
50
+ }.merge(opts)
51
+ Response::ElicitSlot.new(params).to_lex
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Support
7
+ module Mixins
8
+ module SlotElicitation
9
+ def self.included(base)
10
+ base.include(InstanceMethods)
11
+ base.extend(ClassMethods)
12
+ base.attr_accessor(:conversation)
13
+ base.class_attribute(:slots_to_elicit)
14
+ end
15
+
16
+ module InstanceMethods
17
+ def elicit_slots!
18
+ slot_elicitor.elicit!
19
+ end
20
+
21
+ def slots_elicitable?
22
+ slot_elicitor.elicit?
23
+ end
24
+
25
+ private
26
+
27
+ def slot_elicitor
28
+ @slot_elicitor ||= Aws::Lex::Conversation::Slot::Elicitor.new(
29
+ conversation: conversation,
30
+ elicitations: self.class.slots_to_elicit
31
+ )
32
+ end
33
+ end
34
+
35
+ module ClassMethods
36
+ def slot(opts = {})
37
+ self.slots_to_elicit ||= []
38
+ self.slots_to_elicit.push(
39
+ Aws::Lex::Conversation::Slot::Elicitation.new(opts)
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -19,7 +19,8 @@ module Aws
19
19
 
20
20
  module InstanceMethods
21
21
  def assign_attributes!(opts = {})
22
- self.class.attributes.each do |attribute|
22
+ attributes = self.class.attributes | self.class.virtual_attributes
23
+ attributes.each do |attribute|
23
24
  instance_variable_set("@#{attribute}", opts[attribute])
24
25
  end
25
26
  end
@@ -60,6 +61,17 @@ module Aws
60
61
  ->(v) { v.transform_keys(&:to_sym) }
61
62
  end
62
63
 
64
+ def computed_property(attribute, callable)
65
+ mapping[attribute] = attribute
66
+ attr_writer(attribute)
67
+
68
+ # dynamically memoize the result
69
+ define_method(attribute) do
70
+ instance_variable_get("@#{attribute}") ||
71
+ instance_variable_set("@#{attribute}", callable.call(self))
72
+ end
73
+ end
74
+
63
75
  def required(attribute, opts = {})
64
76
  property(attribute, opts.merge(allow_nil: false))
65
77
  end
@@ -76,7 +88,12 @@ module Aws
76
88
 
77
89
  attr_accessor(attribute)
78
90
 
79
- mapping[attribute] = from
91
+ if opts.fetch(:virtual) { false }
92
+ virtual_attributes << attribute
93
+ else
94
+ mapping[attribute] = from
95
+ end
96
+
80
97
  translate(attribute => params)
81
98
  end
82
99
 
@@ -84,6 +101,10 @@ module Aws
84
101
  @attributes ||= mapping.keys
85
102
  end
86
103
 
104
+ def virtual_attributes
105
+ @virtual_attributes ||= []
106
+ end
107
+
87
108
  def mapping
88
109
  @mapping ||= {}
89
110
  end
@@ -8,22 +8,35 @@ module Aws
8
8
  include Base
9
9
 
10
10
  required :name
11
- required :slots
11
+ required :raw_slots, from: :slots, virtual: true
12
12
  required :slot_details
13
13
  required :confirmation_status
14
14
 
15
+ computed_property :slots, ->(instance) do
16
+ instance.raw_slots.each_with_object({}) do |(key, value), hash|
17
+ hash[key.to_sym] = Slot.shrink_wrap(
18
+ name: key,
19
+ value: value,
20
+ # pass a reference to the parent down to the slot so that each slot
21
+ # instance can view a broader scope such as slot_details/resolutions
22
+ current_intent: instance
23
+ )
24
+ end
25
+ end
26
+
15
27
  class << self
16
28
  def slot_details!
17
- ->(v) do
18
- v.each_with_object({}) do |(key, value), hash|
19
- hash[key.to_sym] = SlotDetail.shrink_wrap(value)
20
- end
29
+ ->(val) do
30
+ val
31
+ .reject { |_, v| v.nil? }
32
+ .each_with_object({}) do |(key, value), hash|
33
+ hash[key.to_sym] = SlotDetail.shrink_wrap(value)
34
+ end
21
35
  end
22
36
  end
23
37
  end
24
38
 
25
39
  coerce(
26
- slots: symbolize_hash!,
27
40
  slot_details: slot_details!,
28
41
  confirmation_status: ConfirmationStatus
29
42
  )
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Lex
5
+ class Conversation
6
+ module Type
7
+ class Slot
8
+ include Base
9
+
10
+ required :current_intent, from: :current_intent, virtual: true
11
+ required :name
12
+ required :value
13
+
14
+ def as_json(_opts = {})
15
+ to_lex
16
+ end
17
+
18
+ def to_lex
19
+ value
20
+ end
21
+
22
+ def filled?
23
+ value.to_s != ''
24
+ end
25
+
26
+ def resolve!(index: 0)
27
+ self.value = resolved(index: index)
28
+ end
29
+
30
+ def resolved(index: 0)
31
+ details.resolutions.fetch(index) { SlotResolution.new(value: value) }.value
32
+ end
33
+
34
+ def original_value
35
+ details.original_value
36
+ end
37
+
38
+ def resolvable?
39
+ details.resolutions.any?
40
+ end
41
+
42
+ def details
43
+ @details ||= current_intent.slot_details.fetch(name.to_sym) do
44
+ SlotDetail.new(name: name, resolutions: [], original_value: value)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -3,7 +3,7 @@
3
3
  module Aws
4
4
  module Lex
5
5
  class Conversation
6
- VERSION = '0.5.0'
6
+ VERSION = '1.2.0'
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-lex-conversation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jesse Doyle
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: exe
14
14
  cert_chain: []
15
- date: 2020-06-23 00:00:00.000000000 Z
15
+ date: 2020-07-22 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: shrink_wrap
@@ -64,8 +64,11 @@ files:
64
64
  - lib/aws/lex/conversation/response/delegate.rb
65
65
  - lib/aws/lex/conversation/response/elicit_intent.rb
66
66
  - lib/aws/lex/conversation/response/elicit_slot.rb
67
+ - lib/aws/lex/conversation/slot/elicitation.rb
68
+ - lib/aws/lex/conversation/slot/elicitor.rb
67
69
  - lib/aws/lex/conversation/support/inflector.rb
68
- - lib/aws/lex/conversation/support/responses.rb
70
+ - lib/aws/lex/conversation/support/mixins/responses.rb
71
+ - lib/aws/lex/conversation/support/mixins/slot_elicitation.rb
69
72
  - lib/aws/lex/conversation/type/base.rb
70
73
  - lib/aws/lex/conversation/type/bot.rb
71
74
  - lib/aws/lex/conversation/type/confirmation_status.rb
@@ -87,6 +90,7 @@ files:
87
90
  - lib/aws/lex/conversation/type/sentiment_label.rb
88
91
  - lib/aws/lex/conversation/type/sentiment_response.rb
89
92
  - lib/aws/lex/conversation/type/sentiment_score.rb
93
+ - lib/aws/lex/conversation/type/slot.rb
90
94
  - lib/aws/lex/conversation/type/slot_detail.rb
91
95
  - lib/aws/lex/conversation/type/slot_resolution.rb
92
96
  - lib/aws/lex/conversation/version.rb
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Aws
4
- module Lex
5
- class Conversation
6
- module Support
7
- module Responses
8
- def close(opts = {})
9
- params = {
10
- session_attributes: lex.session_attributes,
11
- recent_intent_summary_view: lex.recent_intent_summary_view
12
- }.merge(opts)
13
- Response::Close.new(params).to_lex
14
- end
15
-
16
- def confirm_intent(opts = {})
17
- params = {
18
- session_attributes: lex.session_attributes,
19
- recent_intent_summary_view: lex.recent_intent_summary_view,
20
- intent_name: lex.current_intent.name,
21
- slots: lex.current_intent.slots
22
- }.merge(opts)
23
- Response::ConfirmIntent.new(params).to_lex
24
- end
25
-
26
- def delegate(opts = {})
27
- params = {
28
- session_attributes: lex.session_attributes,
29
- recent_intent_summary_view: lex.recent_intent_summary_view,
30
- slots: lex.current_intent.slots
31
- }.merge(opts)
32
- Response::Delegate.new(params).to_lex
33
- end
34
-
35
- def elicit_intent(opts = {})
36
- params = {
37
- session_attributes: lex.session_attributes,
38
- recent_intent_summary_view: lex.recent_intent_summary_view
39
- }.merge(opts)
40
- Response::ElicitIntent.new(params).to_lex
41
- end
42
-
43
- def elicit_slot(opts = {})
44
- params = {
45
- session_attributes: lex.session_attributes,
46
- recent_intent_summary_view: lex.recent_intent_summary_view,
47
- slots: lex.current_intent.slots,
48
- intent_name: lex.current_intent.name
49
- }.merge(opts)
50
- Response::ElicitSlot.new(params).to_lex
51
- end
52
- end
53
- end
54
- end
55
- end
56
- end