bot_platform 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.idea/.gitignore +8 -0
- data/.idea/bot_platform.iml +317 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.ruby-version +1 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +21 -0
- data/README.md +19 -9
- data/bin/cli +34 -0
- data/docs/channels.md +7 -0
- data/lib/bot_platform/activity.rb +32 -0
- data/lib/bot_platform/adapter.rb +87 -0
- data/lib/bot_platform/asserts.rb +69 -0
- data/lib/bot_platform/boot.rb +12 -0
- data/lib/bot_platform/channels/base.rb +26 -0
- data/lib/bot_platform/channels/chatwork.rb +1 -0
- data/lib/bot_platform/channels/console.rb +60 -0
- data/lib/bot_platform/channels/facebook.rb +1 -0
- data/lib/bot_platform/channels/line.rb +1 -0
- data/lib/bot_platform/channels/lineworks.rb +102 -0
- data/lib/bot_platform/channels/skype.rb +1 -0
- data/lib/bot_platform/channels/skype_for_business.rb +1 -0
- data/lib/bot_platform/channels/slack.rb +15 -0
- data/lib/bot_platform/channels/teams.rb +15 -0
- data/lib/bot_platform/channels/web.rb +15 -0
- data/lib/bot_platform/channels/wechat.rb +1 -0
- data/lib/bot_platform/channels.rb +9 -0
- data/lib/bot_platform/cli.rb +97 -0
- data/lib/bot_platform/conversation_state.rb +32 -0
- data/lib/bot_platform/dialogs/dialog.rb +48 -0
- data/lib/bot_platform/dialogs/dialog_context.rb +104 -0
- data/lib/bot_platform/dialogs/dialog_instance.rb +15 -0
- data/lib/bot_platform/dialogs/dialog_result.rb +22 -0
- data/lib/bot_platform/dialogs/dialog_set.rb +38 -0
- data/lib/bot_platform/dialogs/dialog_state.rb +11 -0
- data/lib/bot_platform/dialogs/prompts/prompt.rb +84 -0
- data/lib/bot_platform/dialogs/prompts/prompt_options.rb +12 -0
- data/lib/bot_platform/dialogs/prompts/prompt_recognizer_result.rb +16 -0
- data/lib/bot_platform/dialogs/prompts/text_prompt.rb +36 -0
- data/lib/bot_platform/dialogs.rb +17 -0
- data/lib/bot_platform/message_factory.rb +9 -0
- data/lib/bot_platform/state/bot_state.rb +21 -0
- data/lib/bot_platform/state/conversation_state.rb +17 -0
- data/lib/bot_platform/state/user_state.rb +1 -0
- data/lib/bot_platform/storage/memory_storage.rb +51 -0
- data/lib/bot_platform/storage/mysql_storage.rb +1 -0
- data/lib/bot_platform/storage/redis_storage.rb +1 -0
- data/lib/bot_platform/storage/storageable.rb +33 -0
- data/lib/bot_platform/turn_context.rb +105 -0
- data/lib/bot_platform/version.rb +1 -1
- data/lib/bot_platform.rb +11 -0
- data/samples/dialogs/dialog_simple.rb +29 -0
- data/samples/echo.rb +5 -0
- metadata +52 -2
@@ -0,0 +1 @@
|
|
1
|
+
# frozen_string_literal: true
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'readline'
|
5
|
+
require 'getoptlong'
|
6
|
+
require 'singleton'
|
7
|
+
|
8
|
+
module BotPlatform
|
9
|
+
class Cli
|
10
|
+
attr_accessor :bot_name, :user_name, :bot_id, :room_id, :bot_instance
|
11
|
+
|
12
|
+
def initialize(instance)
|
13
|
+
@bot_name = "bot"
|
14
|
+
@user_name = "user"
|
15
|
+
@bot_instance = instance
|
16
|
+
ENV['BOT_CHANNELS'] = 'console'
|
17
|
+
|
18
|
+
Signal.trap(:INT) do
|
19
|
+
puts "\nbye."
|
20
|
+
exit 0
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def run
|
25
|
+
puts welcome_message
|
26
|
+
|
27
|
+
opts = GetoptLong.new(
|
28
|
+
['--name', GetoptLong::OPTIONAL_ARGUMENT]
|
29
|
+
)
|
30
|
+
opts.each do |opt, arg|
|
31
|
+
case opt
|
32
|
+
when '--name' then @bot_name = arg
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
while cmd = Readline.readline(prompt, true)
|
37
|
+
case cmd
|
38
|
+
when "exit", "quit", '\q' then
|
39
|
+
exit
|
40
|
+
when "help", '\h', '\?' then
|
41
|
+
puts help
|
42
|
+
when 'clear', '\c' then
|
43
|
+
system 'clear'
|
44
|
+
else # bot incoming activity
|
45
|
+
unless cmd.empty?
|
46
|
+
process cmd
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def process(cmd)
|
53
|
+
headers = {"X-Bot-Platform-Bot":@bot_name}
|
54
|
+
body = {bot_id:@bot_id, room_id:@room_id}
|
55
|
+
if cmd.start_with? '/'
|
56
|
+
body[:type] = "cmd_back"
|
57
|
+
cmd.slice!(0)
|
58
|
+
body[:cmd] = cmd
|
59
|
+
else
|
60
|
+
body[:type] = "message"
|
61
|
+
body[:text] = cmd
|
62
|
+
end
|
63
|
+
|
64
|
+
instance = @bot_instance
|
65
|
+
|
66
|
+
BotPlatform::Adapter.instance.process_activity headers,body do |context|
|
67
|
+
instance.on_turn context
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
def prompt
|
73
|
+
return "#{@user_name}> "
|
74
|
+
end
|
75
|
+
|
76
|
+
def help
|
77
|
+
<<-USAGE
|
78
|
+
usage:
|
79
|
+
history
|
80
|
+
exit, quit, \\q - close shell and exit
|
81
|
+
help, \\h, \\? - print this usage
|
82
|
+
clear, \\c - clear the terminal screen
|
83
|
+
USAGE
|
84
|
+
end
|
85
|
+
|
86
|
+
def welcome_message
|
87
|
+
<<-WELCOME
|
88
|
+
Welcome to Bot Platform Command Line Interface (v0.1)
|
89
|
+
\\------/
|
90
|
+
_ [| o ω o|] _
|
91
|
+
/ |==\\__-__/=| \\
|
92
|
+
|
93
|
+
powered by Ningfeng Yang
|
94
|
+
WELCOME
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BotPlatform
|
4
|
+
class ConversationState
|
5
|
+
include Singleton
|
6
|
+
attr_accessor :conversation
|
7
|
+
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
user_id = turn_context.from.user_id
|
11
|
+
room_id = turn_context.from.room_id
|
12
|
+
key = "#{user_id}@#{room_id}"
|
13
|
+
user_state = MemoryStorage.instance.get(key) || {}
|
14
|
+
conversation_state = user_state[:conversation] || {}
|
15
|
+
user_state[:conversation] = conversation_state
|
16
|
+
|
17
|
+
MemoryStorage.instance.set(key, user_state)
|
18
|
+
end
|
19
|
+
|
20
|
+
def get(turn_context)
|
21
|
+
user_id = turn_context.from.user_id
|
22
|
+
room_id = turn_context.from.room_id
|
23
|
+
key = "#{user_id}@#{room_id}"
|
24
|
+
|
25
|
+
MemoryStorage.instance.get(key)[:conversation]
|
26
|
+
end
|
27
|
+
|
28
|
+
def set(context, key, value)
|
29
|
+
MemoryStorage.instance.get(context).set(key, value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BotPlatform
|
4
|
+
module Dialogs
|
5
|
+
class Dialog
|
6
|
+
include BotPlatform::Asserts
|
7
|
+
END_OF_TURN = "end_of_turn".freeze
|
8
|
+
|
9
|
+
attr_reader :id
|
10
|
+
|
11
|
+
def initialize(dialog_id)
|
12
|
+
assert_dialog_id_is_valid dialog_id
|
13
|
+
|
14
|
+
@id = dialog_id
|
15
|
+
end
|
16
|
+
|
17
|
+
# abstract
|
18
|
+
# start the instance when ready
|
19
|
+
def start(dc)
|
20
|
+
raise "unimplemented"
|
21
|
+
end
|
22
|
+
|
23
|
+
# called after user has new activity
|
24
|
+
# params:
|
25
|
+
# dc: DialogContext
|
26
|
+
def continue(dc)
|
27
|
+
# by default just stop the dialog
|
28
|
+
return dc.stop
|
29
|
+
end
|
30
|
+
|
31
|
+
# called after pre-dialog was poped
|
32
|
+
# params:
|
33
|
+
# dc: DialogContext
|
34
|
+
def resume(dc, result)
|
35
|
+
# by default just stop the dialog
|
36
|
+
stop result
|
37
|
+
end
|
38
|
+
|
39
|
+
# not-implemented
|
40
|
+
def reprompt(turn_context, instance)
|
41
|
+
end
|
42
|
+
|
43
|
+
# called by #DialogContext#stop_active_dialog if need to stop dialog
|
44
|
+
def stop(result)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BotPlatform
|
4
|
+
module Dialogs
|
5
|
+
class DialogContext
|
6
|
+
include BotPlatform::Asserts
|
7
|
+
|
8
|
+
attr_accessor :dialogs, :turn_context, :dialog_stack
|
9
|
+
|
10
|
+
def initialize(dialogs, turn_context, dialog_state)
|
11
|
+
assert_dialog_set_is_valid dialogs
|
12
|
+
assert_turn_context_is_valid turn_context
|
13
|
+
assert_dialog_state_is_valid dialog_state
|
14
|
+
|
15
|
+
@dialogs = dialogs
|
16
|
+
@turn_context = turn_context
|
17
|
+
@dialog_stack = dialog_state.dialog_stack
|
18
|
+
end
|
19
|
+
|
20
|
+
def active_dialog
|
21
|
+
return nil if @dialog_stack.nil? || @dialog_stack.length==0
|
22
|
+
@dialog_stack.last
|
23
|
+
end
|
24
|
+
|
25
|
+
def start_dialog(dialog_id, options)
|
26
|
+
assert_dialog_id_is_valid dialog_id
|
27
|
+
|
28
|
+
dialog = @dialogs.find(dialog_id)
|
29
|
+
raise "dialog(id=#{dialog_id}) cannot be found." if dialog.nil?
|
30
|
+
dialog_instance = DialogInstance.new dialog_id
|
31
|
+
@dialog_stack.push dialog_instance
|
32
|
+
|
33
|
+
dialog.start self, options
|
34
|
+
end
|
35
|
+
|
36
|
+
def prompt(dialog_id, options)
|
37
|
+
assert_dialog_id_is_valid dialog_id
|
38
|
+
assert_is_not_empty options
|
39
|
+
|
40
|
+
start_dialog dialog_id, options
|
41
|
+
end
|
42
|
+
|
43
|
+
def continue_dialog
|
44
|
+
instance = active_dialog
|
45
|
+
if instance != nil
|
46
|
+
dialog = @dialogs.find instance.dialog_id
|
47
|
+
if dialog
|
48
|
+
return dialog.continue self
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
return DialogResult.new :empty
|
53
|
+
end
|
54
|
+
|
55
|
+
def stop_dialog(result)
|
56
|
+
if @dialog_stack.any?
|
57
|
+
@dialog_stack.pop
|
58
|
+
end
|
59
|
+
dialog = active_dialog
|
60
|
+
|
61
|
+
#previous dialog
|
62
|
+
if dialog
|
63
|
+
dialog.resume self
|
64
|
+
else
|
65
|
+
return DialogResult.new :complete, result
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# stop all dialogs in the stack
|
70
|
+
def stop_all
|
71
|
+
if @dialog_stack.any?
|
72
|
+
@dialog_stack.each do |dialog|
|
73
|
+
dialog.stop
|
74
|
+
end
|
75
|
+
@dialog_stack = []
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# stop active dialog and start a new dialog by given
|
80
|
+
def replace_dialog(dialog_id, options)
|
81
|
+
unless active_dialog.nil?
|
82
|
+
@dialog_stack.pop
|
83
|
+
end
|
84
|
+
|
85
|
+
return start_dialog(dialog_id, options)
|
86
|
+
end
|
87
|
+
|
88
|
+
def reprompt
|
89
|
+
dialog = active_dialog
|
90
|
+
if dialog != nil
|
91
|
+
dialog.reprompt(turn_context)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def stop_active_dialog
|
96
|
+
dialog = active_dialog
|
97
|
+
if dialog != nil
|
98
|
+
dialog.stop
|
99
|
+
@dialog_stack.pop
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BotPlatform
|
4
|
+
module Dialogs
|
5
|
+
class DialogResult
|
6
|
+
STATUS_TYPES = {
|
7
|
+
:empty => 0,
|
8
|
+
:complete => 1,
|
9
|
+
:canceled => 2,
|
10
|
+
}
|
11
|
+
|
12
|
+
attr_reader :status, :result
|
13
|
+
|
14
|
+
def initialize(status, result=nil)
|
15
|
+
raise "invalid dialog result status" if !STATUS_TYPES.key?(status)
|
16
|
+
|
17
|
+
@status = status
|
18
|
+
@result = result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BotPlatform
|
4
|
+
module Dialogs
|
5
|
+
class DialogSet
|
6
|
+
include BotPlatform::Asserts
|
7
|
+
|
8
|
+
attr_accessor :dialogs, :dialog_state
|
9
|
+
|
10
|
+
def initialize()
|
11
|
+
# assert_dialog_state_is_not_null dialog_state
|
12
|
+
# @dialog_state = dialog_state
|
13
|
+
@dialog_state = BotPlatform::Dialogs::DialogState.new
|
14
|
+
@dialogs = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(dialog)
|
18
|
+
assert_dialog_is_valid dialog
|
19
|
+
assert_dialog_is_uniq @dialogs, dialog.id
|
20
|
+
|
21
|
+
@dialogs[dialog.id.to_sym] = dialog
|
22
|
+
return self
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_dialog_context(turn_context)
|
26
|
+
assert_turn_context_is_valid turn_context
|
27
|
+
|
28
|
+
state = @dialog_state
|
29
|
+
|
30
|
+
return DialogContext.new self, turn_context, state
|
31
|
+
end
|
32
|
+
|
33
|
+
def find(dialog_id)
|
34
|
+
return @dialogs[dialog_id.to_sym]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BotPlatform
|
4
|
+
module Dialogs
|
5
|
+
module Prompts
|
6
|
+
class Prompt < Dialog
|
7
|
+
attr_reader :validator
|
8
|
+
|
9
|
+
def initialize(dialog_id, validtor=nil)
|
10
|
+
super(dialog_id)
|
11
|
+
@validator = validator
|
12
|
+
end
|
13
|
+
|
14
|
+
def start(dc, options)
|
15
|
+
assert_dialog_context_is_valid dc
|
16
|
+
assert_prompt_options_is_valid options
|
17
|
+
|
18
|
+
state = dc.active_dialog.state
|
19
|
+
state[:options] = options
|
20
|
+
state[:state] = {}
|
21
|
+
|
22
|
+
on_prompt(dc.turn_context, state[:state], state[:options], false)
|
23
|
+
return BotPlatform::Dialogs::DialogResult.new :complete
|
24
|
+
end
|
25
|
+
|
26
|
+
def continue(dc)
|
27
|
+
assert_dialog_context_is_valid dc
|
28
|
+
|
29
|
+
if dc.turn_context.activity.type != BotPlatform::Activity::TYPES[:message]
|
30
|
+
# do nothing
|
31
|
+
return BotPlatform::Dialogs::DialogResult.new :complete
|
32
|
+
end
|
33
|
+
|
34
|
+
instance = dc.active_dialog
|
35
|
+
state = instance.state[:state]
|
36
|
+
options = instance.state[:options]
|
37
|
+
|
38
|
+
recognized = on_recognize(dc.turn_context, state, options)
|
39
|
+
|
40
|
+
is_valid = false
|
41
|
+
|
42
|
+
if !@validator.nil?
|
43
|
+
prompt_validator_ctx = PromptValidatorContext.new dc.turn_context, recognized, state, options
|
44
|
+
is_valid = @validator.send(prompt_validator_ctx)
|
45
|
+
elsif recognized.succeeded
|
46
|
+
is_valid = true
|
47
|
+
end
|
48
|
+
|
49
|
+
if is_valid
|
50
|
+
return dc.stop_dialog(recognized.value)
|
51
|
+
else
|
52
|
+
return on_prompt(dc.turn_context, state, options, true)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def resume(dc, result=null)
|
57
|
+
assert_dialog_context_is_valid dc
|
58
|
+
|
59
|
+
reprompt(dc.turn_context, dc.active_dialog)
|
60
|
+
return BotPlatform::Dialogs::DialogResult.new :complete
|
61
|
+
end
|
62
|
+
|
63
|
+
def repromp(ttx, instance)
|
64
|
+
assert_turn_context_is_valid ttx
|
65
|
+
assert_dialog_instance_is_valid instance
|
66
|
+
|
67
|
+
state = instace.state[:state]
|
68
|
+
options = instance.state[:options]
|
69
|
+
|
70
|
+
on_prompt(ttx, state, options, false)
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_prompt(turn_context, state, options, is_retry)
|
74
|
+
raise "not implemented(abstrct in super)"
|
75
|
+
end
|
76
|
+
|
77
|
+
def on_recognize(turn_context, state, options, is_retry)
|
78
|
+
raise "not implemented(abstrct in super)"
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|