flow_chat 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/README.md +130 -11
- data/flow_chat.gemspec +3 -3
- data/images/ussd_simulator.png +0 -0
- data/lib/flow_chat/context.rb +2 -0
- data/lib/flow_chat/flow.rb +9 -0
- data/lib/flow_chat/session/rails_session_store.rb +2 -2
- data/lib/flow_chat/ussd/app.rb +3 -3
- 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/ussd/simulator/views/simulator.html.erb +5 -5
- data/lib/flow_chat/version.rb +1 -1
- metadata +12 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c1e5cd739c50f112b6a5637e712756e0b96bdb18c2276c7d30db2b2a071cc23
|
4
|
+
data.tar.gz: f701ad5050599c06b87eba80841823b04eef156e7ea3e77527a0be91519b0d48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef7e1721b7c5453130999b2ef899af4f20ba6db81ac4b3956ce7ade5956bf2b61e248b2be66ace885c633d4d612025208b31d1ff081ef1a0d671f579e0d3db38
|
7
|
+
data.tar.gz: 2581189b102229a0e8843691f38eaf91a9c7bf1d2db7c7a71e685124a849621cf55bdd33c6cdd1c928e2bfac051bca597601875dc9cff921063a98ff954a7b82
|
data/.DS_Store
ADDED
Binary file
|
data/README.md
CHANGED
@@ -1,28 +1,147 @@
|
|
1
1
|
# FlowChat
|
2
2
|
|
3
|
-
|
3
|
+
FlowChat is a Rails framework designed for crafting Menu-based conversation workflows, such as those used in USSD systems. It introduces an intuitive approach to defining conversation flows in Ruby, facilitating clear and logical flow development. Currently supporting USSD with plans to extend functionality to WhatsApp and Telegram, FlowChat makes multi-channel user interaction seamless and efficient.
|
4
4
|
|
5
|
-
|
5
|
+
The framework's architecture leverages a middleware processing pipeline, offering flexibility in customizing the conversation handling process.
|
6
6
|
|
7
|
-
##
|
7
|
+
## Getting Started
|
8
8
|
|
9
|
-
|
9
|
+
### Installation
|
10
|
+
|
11
|
+
Incorporate FlowChat into your Rails project by adding the following line to your Gemfile:
|
10
12
|
|
11
13
|
```ruby
|
12
|
-
gem 'flow_chat'
|
14
|
+
gem 'flow_chat', '~> 0.2.0'
|
15
|
+
```
|
16
|
+
|
17
|
+
Then, execute:
|
18
|
+
|
19
|
+
```bash
|
20
|
+
bundle install
|
13
21
|
```
|
14
22
|
|
15
|
-
|
23
|
+
Alternatively, you can install it directly using:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
gem install flow_chat
|
27
|
+
```
|
28
|
+
|
29
|
+
### Basic Usage
|
30
|
+
|
31
|
+
#### Building Your First Flow
|
32
|
+
|
33
|
+
Create a new class derived from `FlowChat::Flow` to define your conversation flow. It's recommended to place your flow definitions under `app/flow_chat`.
|
34
|
+
|
35
|
+
For a simple "Hello World" flow:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
class HelloWorldFlow < FlowChat::Flow
|
39
|
+
def main_page
|
40
|
+
app.say "Hello World!"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
The `app` instance within `FlowChat::Flow` provides methods to interact with and respond to the user, such as `app.say`, which sends a message to the user.
|
46
|
+
|
47
|
+
#### Integration with USSD
|
48
|
+
|
49
|
+
Given that most USSD gateways interact via HTTP, set up a controller to handle the conversation flow:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
class UssdDemoController < ApplicationController
|
53
|
+
skip_forgery_protection
|
54
|
+
|
55
|
+
def hello_world
|
56
|
+
ussd_processor.run HelloWorldFlow, :main_page
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def ussd_processor
|
62
|
+
@ussd_processor ||= FlowChat::Ussd::Processor.new(self) do |processor|
|
63
|
+
processor.use_gateway FlowChat::Ussd::Gateway::Nalo
|
64
|
+
processor.use_session_store FlowChat::Session::RailsSessionStore
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
This controller initializes a `FlowChat::Ussd::Processor` specifying the use of Nalo Solutions' gateway and a session storage mechanism. Here, `RailsSessionStore` is chosen for simplicity and demonstration purposes.
|
71
|
+
|
72
|
+
Bind the controller action to a route:
|
16
73
|
|
17
|
-
|
74
|
+
```ruby
|
75
|
+
Rails.application.routes.draw do
|
76
|
+
post 'ussd_hello_world' => 'ussd_demo#hello_world'
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
#### Testing with the USSD Simulator
|
81
|
+
|
82
|
+
FlowChat comes with a USSD simulator for local testing:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
class UssdSimulatorController < ApplicationController
|
86
|
+
include FlowChat::Ussd::Simulator::Controller
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def default_endpoint
|
91
|
+
'/ussd_hello_world'
|
92
|
+
end
|
93
|
+
|
94
|
+
def default_provider
|
95
|
+
:nalo
|
96
|
+
end
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
And set up the corresponding route:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
Rails.application.routes.draw do
|
104
|
+
get 'ussd_simulator' => 'ussd_simulator#ussd_simulator'
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
Visit [http://localhost:3000/ussd_simulator](http://localhost:3000/ussd_simulator) to initiate and test your flow.
|
109
|
+
|
110
|
+
### Advanced Usage: Implementing Multiple Screens
|
111
|
+
|
112
|
+
To engage users with a multi-step interaction, define a flow with multiple screens:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
class MultipleScreensFlow < FlowChat::Flow
|
116
|
+
def main_page
|
117
|
+
name = app.screen(:name) { |prompt|
|
118
|
+
prompt.ask "What is your name?", transform: ->(input) { input.squish }
|
119
|
+
}
|
120
|
+
|
121
|
+
age = app.screen(:age) do |prompt|
|
122
|
+
prompt.ask "How old are you?",
|
123
|
+
convert: ->(input) { input.to_i },
|
124
|
+
validate: ->(input) { "You must be at least 13 years old" unless input >= 13 }
|
125
|
+
end
|
126
|
+
|
127
|
+
gender = app.screen(:gender) { |prompt| prompt.select "What is your gender", ["Male", "Female"] }
|
128
|
+
|
129
|
+
confirm = app.screen(:confirm) do |prompt|
|
130
|
+
prompt.yes?("Is this correct?\n\nName: #{name}\nAge: #{age}\nGender: #{gender}")
|
131
|
+
end
|
132
|
+
|
133
|
+
app.say confirm ? "Thank you for confirming" : "Please try again"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
```
|
18
137
|
|
19
|
-
|
138
|
+
This example illustrates a flow that collects and confirms user information across multiple interaction steps, showcasing FlowChat's capability to handle complex conversation logic effortlessly.
|
20
139
|
|
21
|
-
|
140
|
+
TODO:
|
22
141
|
|
23
|
-
|
142
|
+
### Sub Flows
|
24
143
|
|
25
|
-
TODO:
|
144
|
+
TODO:
|
26
145
|
|
27
146
|
## Development
|
28
147
|
|
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 building Menu based conversations (e.g. USSD) in Rails."
|
10
|
+
spec.description = "Framework for building 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
|
|
Binary file
|
data/lib/flow_chat/context.rb
CHANGED
data/lib/flow_chat/ussd/app.rb
CHANGED
@@ -5,8 +5,8 @@ module FlowChat
|
|
5
5
|
|
6
6
|
def initialize(context)
|
7
7
|
@context = context
|
8
|
-
@session = context
|
9
|
-
@input = context
|
8
|
+
@session = context.session
|
9
|
+
@input = context.input
|
10
10
|
@navigation_stack = []
|
11
11
|
end
|
12
12
|
|
@@ -25,7 +25,7 @@ module FlowChat
|
|
25
25
|
value
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
28
|
+
def say(msg)
|
29
29
|
raise FlowChat::Interrupt::Terminate.new(msg)
|
30
30
|
end
|
31
31
|
end
|
@@ -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
|
@@ -40,22 +40,22 @@
|
|
40
40
|
</head>
|
41
41
|
<body>
|
42
42
|
<div class="content">
|
43
|
-
<div class="field
|
43
|
+
<div class="field <%= show_options ? '' : 'hidden' %>">
|
44
44
|
<div class="label">Provider </div>
|
45
45
|
<div class="value">
|
46
46
|
<select id="provider">
|
47
|
-
<option <%=
|
48
|
-
<option <%=
|
47
|
+
<option <%= default_provider == :nalo ? 'selected' : '' %> value="nalo">Nalo</option>
|
48
|
+
<option <%= default_provider == :nsano ? 'selected' : '' %> value="nsano">Nsano</option>
|
49
49
|
</select>
|
50
50
|
</div>
|
51
51
|
</div>
|
52
|
-
<div class="field <%=
|
52
|
+
<div class="field <%= show_options ? '' : 'hidden' %>">
|
53
53
|
<div class="label">Endpoint </div>
|
54
54
|
<div class="value">
|
55
55
|
<input id="endpoint" value="<%= default_endpoint %>" />
|
56
56
|
</div>
|
57
57
|
</div>
|
58
|
-
<div class="field <%=
|
58
|
+
<div class="field <%= show_options ? '' : 'hidden' %>">
|
59
59
|
<div class="label">MSISDN </div>
|
60
60
|
<div class="value">
|
61
61
|
<input id="msisdn" value="<%= default_msisdn %>" />
|
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.1
|
4
|
+
version: 0.2.1
|
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,13 +80,14 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 0.4.2
|
83
|
-
description:
|
83
|
+
description: Framework for building Menu based conversations (e.g. USSD) in Rails.
|
84
84
|
email:
|
85
85
|
- sfroelich01@gmail.com
|
86
86
|
executables: []
|
87
87
|
extensions: []
|
88
88
|
extra_rdoc_files: []
|
89
89
|
files:
|
90
|
+
- ".DS_Store"
|
90
91
|
- ".gitignore"
|
91
92
|
- ".rspec"
|
92
93
|
- ".travis.yml"
|
@@ -97,9 +98,11 @@ files:
|
|
97
98
|
- bin/console
|
98
99
|
- bin/setup
|
99
100
|
- flow_chat.gemspec
|
101
|
+
- images/ussd_simulator.png
|
100
102
|
- lib/flow_chat.rb
|
101
103
|
- lib/flow_chat/config.rb
|
102
104
|
- lib/flow_chat/context.rb
|
105
|
+
- lib/flow_chat/flow.rb
|
103
106
|
- lib/flow_chat/interrupt.rb
|
104
107
|
- lib/flow_chat/session/middleware.rb
|
105
108
|
- lib/flow_chat/session/rails_session_store.rb
|
@@ -111,17 +114,18 @@ files:
|
|
111
114
|
- lib/flow_chat/ussd/middleware/resumable_session.rb
|
112
115
|
- lib/flow_chat/ussd/processor.rb
|
113
116
|
- lib/flow_chat/ussd/prompt.rb
|
117
|
+
- lib/flow_chat/ussd/renderer.rb
|
114
118
|
- lib/flow_chat/ussd/simulator/controller.rb
|
115
119
|
- lib/flow_chat/ussd/simulator/views/simulator.html.erb
|
116
120
|
- lib/flow_chat/version.rb
|
117
|
-
homepage: https://github.com/
|
121
|
+
homepage: https://github.com/radioactive-labs/flow_chat
|
118
122
|
licenses:
|
119
123
|
- MIT
|
120
124
|
metadata:
|
121
125
|
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/
|
126
|
+
homepage_uri: https://github.com/radioactive-labs/flow_chat
|
127
|
+
source_code_uri: https://github.com/radioactive-labs/flow_chat
|
128
|
+
changelog_uri: https://github.com/radioactive-labs/flow_chat
|
125
129
|
post_install_message:
|
126
130
|
rdoc_options: []
|
127
131
|
require_paths:
|
@@ -140,5 +144,5 @@ requirements: []
|
|
140
144
|
rubygems_version: 3.5.6
|
141
145
|
signing_key:
|
142
146
|
specification_version: 4
|
143
|
-
summary:
|
147
|
+
summary: Framework for building Menu based conversations (e.g. USSD) in Rails.
|
144
148
|
test_files: []
|