flow_chat 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/flow_chat.gemspec +3 -3
- data/lib/flow_chat/context.rb +2 -0
- data/lib/flow_chat/session/rails_session_store.rb +2 -2
- data/lib/flow_chat/ussd/app.rb +2 -2
- data/lib/flow_chat/ussd/gateway/nalo.rb +4 -10
- data/lib/flow_chat/ussd/middleware/pagination.rb +51 -47
- data/lib/flow_chat/ussd/processor.rb +10 -9
- data/lib/flow_chat/ussd/renderer.rb +26 -0
- data/lib/flow_chat/version.rb +1 -1
- metadata +9 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6eba909e6ae38dfeb26be6f854b185ca2c8097397aa4eea8a094d47e1d3fcd56
|
4
|
+
data.tar.gz: c47e6bf434684b87c7f0c8b2ebb9b6536c5bc17efe46fa3bec3876b976401f96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f52ba0115aa8000f9de120d436c9800318c8635efa86979b17f2dc01104d6ae65f66dcc193ada68a0a36ff238e9b6a87ef9ee66e00581ca4b5faa72a31fb25a0
|
7
|
+
data.tar.gz: 508138465e83f318e8e889f4e11ea19a709fc06c310b76c44388f56b84c94521fa44b45cc734fcfc07acf3b1cf38bbc764614e1d7252ee7b40ab4e0a20c55877
|
data/flow_chat.gemspec
CHANGED
@@ -6,9 +6,9 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.authors = ["Stefan Froelich"]
|
7
7
|
spec.email = ["sfroelich01@gmail.com"]
|
8
8
|
|
9
|
-
spec.summary = "
|
10
|
-
spec.description = "
|
11
|
-
spec.homepage = "https://github.com/
|
9
|
+
spec.summary = "Framework for processing Menu based conversations e.g. USSD in Rails."
|
10
|
+
spec.description = "Framework for processing Menu based conversations e.g. USSD in Rails."
|
11
|
+
spec.homepage = "https://github.com/radioactive-labs/flow_chat"
|
12
12
|
spec.license = "MIT"
|
13
13
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
14
14
|
|
data/lib/flow_chat/context.rb
CHANGED
data/lib/flow_chat/ussd/app.rb
CHANGED
@@ -18,26 +18,20 @@ module FlowChat
|
|
18
18
|
# context["request.type"] = params["MSGTYPE"] ? :initial : :response
|
19
19
|
context["request.input"] = params["USERDATA"].presence
|
20
20
|
|
21
|
-
type,
|
21
|
+
type, prompt, choices = @app.call(context)
|
22
22
|
|
23
23
|
context.controller.render json: {
|
24
24
|
USERID: params["USERID"],
|
25
25
|
MSISDN: params["MSISDN"],
|
26
|
-
MSG:
|
26
|
+
MSG: render_prompt(prompt, choices),
|
27
27
|
MSGTYPE: type == :prompt
|
28
28
|
}
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
|
-
def
|
34
|
-
|
35
|
-
end
|
36
|
-
|
37
|
-
def build_choices(choices)
|
38
|
-
return unless choices.present?
|
39
|
-
|
40
|
-
choices.map { |i, c| "#{i}. #{c}" }.join "\n"
|
33
|
+
def render_prompt(prompt, choices)
|
34
|
+
FlowChat::Ussd::Renderer.new(prompt, choices).render
|
41
35
|
end
|
42
36
|
end
|
43
37
|
end
|
@@ -11,17 +11,16 @@ module FlowChat
|
|
11
11
|
@session = context.session
|
12
12
|
|
13
13
|
if intercept?
|
14
|
-
|
15
|
-
[
|
14
|
+
type, prompt = handle_intercepted_request
|
15
|
+
[type, prompt, []]
|
16
16
|
else
|
17
17
|
@session.delete "ussd.pagination"
|
18
|
-
|
18
|
+
type, prompt, choices = @app.call(context)
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
end
|
20
|
+
prompt = FlowChat::Ussd::Renderer.new(prompt, choices).render
|
21
|
+
type, prompt = maybe_paginate(type, prompt) if prompt.present?
|
23
22
|
|
24
|
-
|
23
|
+
[type, prompt, []]
|
25
24
|
end
|
26
25
|
end
|
27
26
|
|
@@ -29,57 +28,59 @@ module FlowChat
|
|
29
28
|
|
30
29
|
def intercept?
|
31
30
|
pagination_state.present? &&
|
32
|
-
(pagination_state[
|
33
|
-
([Config.pagination_next_option, Config.pagination_back_option].include? @context
|
31
|
+
(pagination_state["type"].to_sym == :terminal ||
|
32
|
+
([Config.pagination_next_option, Config.pagination_back_option].include? @context.input))
|
34
33
|
end
|
35
34
|
|
36
35
|
def handle_intercepted_request
|
37
36
|
Config.logger&.info "FlowChat::Middleware::Pagination :: Intercepted to handle pagination"
|
38
37
|
start, finish, has_more = calculate_offsets
|
39
|
-
type = (pagination_state[
|
40
|
-
|
38
|
+
type = (pagination_state["type"].to_sym == :terminal && !has_more) ? :terminal : :prompt
|
39
|
+
prompt = pagination_state["prompt"][start..finish].strip + build_pagination_options(type, has_more)
|
41
40
|
set_pagination_state(current_page, start, finish)
|
42
41
|
|
43
|
-
|
42
|
+
[type, prompt]
|
44
43
|
end
|
45
44
|
|
46
|
-
def maybe_paginate(
|
47
|
-
if
|
48
|
-
|
49
|
-
|
45
|
+
def maybe_paginate(type, prompt)
|
46
|
+
if prompt.length > Config.pagination_page_size
|
47
|
+
original_prompt = prompt
|
48
|
+
Config.logger&.info "FlowChat::Middleware::Pagination :: Response length (#{prompt.length}) exceeds page size (#{Config.pagination_page_size}). Paginating."
|
49
|
+
prompt = prompt[0..single_option_slice_size]
|
50
50
|
# Ensure we do not cut words and options off in the middle.
|
51
|
-
current_pagebreak =
|
52
|
-
set_pagination_state(1, 0, current_pagebreak,
|
53
|
-
|
54
|
-
|
51
|
+
current_pagebreak = prompt[single_option_slice_size + 1].blank? ? single_option_slice_size : prompt.rindex("\n") || prompt.rindex(" ") || single_option_slice_size
|
52
|
+
set_pagination_state(1, 0, current_pagebreak, original_prompt, type)
|
53
|
+
prompt = prompt[0..current_pagebreak].strip + "\n\n" + next_option
|
54
|
+
type = :prompt
|
55
55
|
end
|
56
|
-
|
56
|
+
[type, prompt]
|
57
57
|
end
|
58
58
|
|
59
59
|
def calculate_offsets
|
60
60
|
page = current_page
|
61
|
-
offset = pagination_state[
|
61
|
+
offset = pagination_state["offsets"][page.to_s]
|
62
62
|
if offset.present?
|
63
63
|
Config.logger&.debug "FlowChat::Middleware::Pagination :: Reusing cached offset for page: #{page}"
|
64
|
-
start = offset[
|
65
|
-
finish = offset[
|
66
|
-
has_more = pagination_state[
|
64
|
+
start = offset["start"]
|
65
|
+
finish = offset["finish"]
|
66
|
+
has_more = pagination_state["prompt"].length > finish
|
67
67
|
else
|
68
68
|
Config.logger&.debug "FlowChat::Middleware::Pagination :: Calculating offset for page: #{page}"
|
69
69
|
# We are guaranteed a previous offset because it was set in maybe_paginate
|
70
|
-
|
71
|
-
|
72
|
-
|
70
|
+
previous_page = page - 1
|
71
|
+
previous_offset = pagination_state["offsets"][previous_page.to_s]
|
72
|
+
start = previous_offset["finish"] + 1
|
73
|
+
has_more, len = (pagination_state["prompt"].length > start + single_option_slice_size) ? [true, dual_options_slice_size] : [false, single_option_slice_size]
|
73
74
|
finish = start + len
|
74
|
-
if start > pagination_state[
|
75
|
+
if start > pagination_state["prompt"].length
|
75
76
|
Config.logger&.debug "FlowChat::Middleware::Pagination :: No content exists for page: #{page}. Reverting to page: #{page - 1}"
|
76
77
|
page -= 1
|
77
78
|
has_more = false
|
78
|
-
start = previous_offset[
|
79
|
-
finish = previous_offset[
|
79
|
+
start = previous_offset["start"]
|
80
|
+
finish = previous_offset["finish"]
|
80
81
|
else
|
81
|
-
|
82
|
-
current_pagebreak = pagination_state[
|
82
|
+
prompt = pagination_state["prompt"][start..finish]
|
83
|
+
current_pagebreak = pagination_state["prompt"][finish + 1].blank? ? len : prompt.rindex("\n") || prompt.rindex(" ") || len
|
83
84
|
finish = start + current_pagebreak
|
84
85
|
end
|
85
86
|
end
|
@@ -126,28 +127,31 @@ module FlowChat
|
|
126
127
|
end
|
127
128
|
|
128
129
|
def current_page
|
129
|
-
|
130
|
-
if @context
|
131
|
-
|
132
|
-
elsif @context
|
133
|
-
|
130
|
+
page = pagination_state["page"]
|
131
|
+
if @context.input == Config.pagination_back_option
|
132
|
+
page -= 1
|
133
|
+
elsif @context.input == Config.pagination_next_option
|
134
|
+
page += 1
|
134
135
|
end
|
135
|
-
[
|
136
|
+
[page, 1].max
|
136
137
|
end
|
137
138
|
|
138
139
|
def pagination_state
|
139
|
-
@context.session.get("pagination"
|
140
|
+
@pagination_state ||= @context.session.get("ussd.pagination") || {}
|
140
141
|
end
|
141
142
|
|
142
|
-
def set_pagination_state(page, offset_start, offset_finish,
|
143
|
-
offsets = pagination_state[
|
143
|
+
def set_pagination_state(page, offset_start, offset_finish, prompt = nil, type = nil)
|
144
|
+
offsets = pagination_state["offsets"] || {}
|
144
145
|
offsets[page] = {start: offset_start, finish: offset_finish}
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
146
|
+
prompt ||= pagination_state["prompt"]
|
147
|
+
type ||= pagination_state["type"]
|
148
|
+
@pagination_state = {
|
149
|
+
"page" => page,
|
150
|
+
"offsets" => offsets,
|
151
|
+
"prompt" => prompt,
|
152
|
+
"type" => type
|
150
153
|
}
|
154
|
+
@session.set "ussd.pagination", @pagination_state
|
151
155
|
end
|
152
156
|
end
|
153
157
|
end
|
@@ -33,21 +33,22 @@ module FlowChat
|
|
33
33
|
self
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
def run(flow, action)
|
41
|
-
@context["flow.class"] = flow
|
36
|
+
def run(flow_class, action)
|
37
|
+
@context["flow.name"] = flow_class.name.underscore
|
38
|
+
@context["flow.class"] = flow_class
|
42
39
|
@context["flow.action"] = action
|
43
40
|
|
44
|
-
::Middleware::Builder.new name: "ussd" do |b|
|
41
|
+
stack = ::Middleware::Builder.new name: "ussd" do |b|
|
45
42
|
b.use gateway
|
46
43
|
b.use FlowChat::Session::Middleware
|
47
|
-
|
44
|
+
b.use FlowChat::Ussd::Middleware::Pagination
|
48
45
|
b.use middleware
|
49
46
|
b.use FlowChat::Ussd::Middleware::Executor
|
50
|
-
end.inject_logger(Rails.logger)
|
47
|
+
end.inject_logger(Rails.logger)
|
48
|
+
|
49
|
+
yield stack if block_given?
|
50
|
+
|
51
|
+
stack.call(@context)
|
51
52
|
end
|
52
53
|
end
|
53
54
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module FlowChat
|
2
|
+
module Ussd
|
3
|
+
class Renderer
|
4
|
+
attr_reader :prompt, :choices
|
5
|
+
|
6
|
+
def initialize(prompt, choices)
|
7
|
+
@prompt = prompt
|
8
|
+
@choices = choices
|
9
|
+
end
|
10
|
+
|
11
|
+
def render = build_prompt
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def build_prompt
|
16
|
+
[prompt, build_choices].compact.join "\n\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_choices
|
20
|
+
return unless choices.present?
|
21
|
+
|
22
|
+
choices.map { |i, c| "#{i}. #{c}" }.join "\n"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/flow_chat/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flow_chat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Froelich
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-04-
|
11
|
+
date: 2024-04-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zeitwerk
|
@@ -80,7 +80,7 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 0.4.2
|
83
|
-
description:
|
83
|
+
description: Framework for processing Menu based conversations e.g. USSD in Rails.
|
84
84
|
email:
|
85
85
|
- sfroelich01@gmail.com
|
86
86
|
executables: []
|
@@ -111,17 +111,18 @@ files:
|
|
111
111
|
- lib/flow_chat/ussd/middleware/resumable_session.rb
|
112
112
|
- lib/flow_chat/ussd/processor.rb
|
113
113
|
- lib/flow_chat/ussd/prompt.rb
|
114
|
+
- lib/flow_chat/ussd/renderer.rb
|
114
115
|
- lib/flow_chat/ussd/simulator/controller.rb
|
115
116
|
- lib/flow_chat/ussd/simulator/views/simulator.html.erb
|
116
117
|
- lib/flow_chat/version.rb
|
117
|
-
homepage: https://github.com/
|
118
|
+
homepage: https://github.com/radioactive-labs/flow_chat
|
118
119
|
licenses:
|
119
120
|
- MIT
|
120
121
|
metadata:
|
121
122
|
allowed_push_host: https://rubygems.org
|
122
|
-
homepage_uri: https://github.com/
|
123
|
-
source_code_uri: https://github.com/
|
124
|
-
changelog_uri: https://github.com/
|
123
|
+
homepage_uri: https://github.com/radioactive-labs/flow_chat
|
124
|
+
source_code_uri: https://github.com/radioactive-labs/flow_chat
|
125
|
+
changelog_uri: https://github.com/radioactive-labs/flow_chat
|
125
126
|
post_install_message:
|
126
127
|
rdoc_options: []
|
127
128
|
require_paths:
|
@@ -140,5 +141,5 @@ requirements: []
|
|
140
141
|
rubygems_version: 3.5.6
|
141
142
|
signing_key:
|
142
143
|
specification_version: 4
|
143
|
-
summary:
|
144
|
+
summary: Framework for processing Menu based conversations e.g. USSD in Rails.
|
144
145
|
test_files: []
|