flow_chat 0.8.1 → 0.9.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/.cliff.toml +74 -0
- data/.github/workflows/ci.yml +2 -3
- data/.github/workflows/release.yml +56 -0
- data/.standard.yml +4 -0
- data/CHANGELOG.md +22 -0
- data/CLAUDE.md +327 -0
- data/CONTRIBUTING.md +134 -0
- data/Gemfile +1 -0
- data/README.md +290 -105
- data/Rakefile +5 -1
- data/SECURITY.md +42 -349
- data/docs/architecture.md +510 -0
- data/docs/async-background-processing.md +298 -0
- data/docs/configuration.md +556 -226
- data/docs/factory-pattern.md +355 -0
- data/docs/gateway-context-variables.md +171 -0
- data/docs/gateway-development.md +723 -0
- data/docs/getting-started.md +429 -0
- data/docs/instrumentation.md +264 -153
- data/docs/platforms/telegram.md +1013 -0
- data/docs/platforms/ussd.md +693 -0
- data/docs/platforms/whatsapp.md +1395 -0
- data/docs/testing.md +243 -365
- data/examples/custom_session_id_example.rb +119 -0
- data/examples/http_controller.rb +154 -0
- data/examples/intercom_configuration_example.rb +118 -0
- data/examples/intercom_controller.rb +194 -0
- data/examples/multi_tenant_whatsapp_controller.rb +4 -4
- data/examples/simulator_controller.rb +21 -1
- data/examples/ussd_controller.rb +10 -10
- data/examples/whatsapp_controller.rb +2 -2
- data/flow_chat.gemspec +4 -0
- data/lib/flow_chat/{base_app.rb → app.rb} +28 -9
- data/lib/flow_chat/async_job.rb +176 -0
- data/lib/flow_chat/config.rb +23 -28
- data/lib/flow_chat/{base_executor.rb → executor.rb} +7 -12
- data/lib/flow_chat/factory.rb +94 -0
- data/lib/flow_chat/gateway_async_support.rb +106 -0
- data/lib/flow_chat/generic_async_job.rb +30 -0
- data/lib/flow_chat/http/gateway/simple.rb +125 -0
- data/lib/flow_chat/http/renderer.rb +41 -0
- data/lib/flow_chat/instrumentation/setup.rb +1 -1
- data/lib/flow_chat/instrumentation.rb +25 -0
- data/lib/flow_chat/intercom/client.rb +155 -0
- data/lib/flow_chat/intercom/configuration.rb +149 -0
- data/lib/flow_chat/intercom/gateway/intercom_api.rb +396 -0
- data/lib/flow_chat/intercom/renderer.rb +71 -0
- data/lib/flow_chat/phone_number_util.rb +49 -0
- data/lib/flow_chat/processor.rb +188 -0
- data/lib/flow_chat/renderers/markdown_support.rb +58 -0
- data/lib/flow_chat/session/cache_session_store.rb +1 -17
- data/lib/flow_chat/session/middleware.rb +43 -26
- data/lib/flow_chat/simulator/controller.rb +17 -5
- data/lib/flow_chat/simulator/views/simulator.html.erb +220 -8
- data/lib/flow_chat/telegram/client.rb +240 -0
- data/lib/flow_chat/telegram/configuration.rb +118 -0
- data/lib/flow_chat/telegram/gateway/bot_api.rb +300 -0
- data/lib/flow_chat/telegram/middleware/choice_mapper.rb +35 -0
- data/lib/flow_chat/telegram/renderer.rb +125 -0
- data/lib/flow_chat/telegram.rb +7 -0
- data/lib/flow_chat/ussd/gateway/nalo.rb +27 -11
- data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
- data/lib/flow_chat/ussd/renderer.rb +1 -1
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/client.rb +144 -13
- data/lib/flow_chat/whatsapp/configuration.rb +1 -1
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +144 -106
- data/lib/flow_chat/whatsapp/id_generator.rb +124 -0
- data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +147 -0
- data/lib/flow_chat/whatsapp/renderer.rb +145 -11
- data/lib/flow_chat.rb +11 -1
- data/lib/tasks/release.rake +165 -0
- metadata +88 -21
- data/docs/flows.md +0 -320
- data/docs/images/simulator.png +0 -0
- data/docs/media.md +0 -153
- data/docs/sessions.md +0 -433
- data/docs/ussd-setup.md +0 -322
- data/docs/whatsapp-setup.md +0 -162
- data/examples/whatsapp_message_job.rb +0 -113
- data/lib/flow_chat/base_processor.rb +0 -145
- data/lib/flow_chat/session/rails_session_store.rb +0 -68
- data/lib/flow_chat/ussd/app.rb +0 -6
- data/lib/flow_chat/ussd/gateway/nsano.rb +0 -98
- data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
- data/lib/flow_chat/ussd/processor.rb +0 -39
- data/lib/flow_chat/whatsapp/app.rb +0 -29
- data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
- data/lib/flow_chat/whatsapp/processor.rb +0 -32
- data/lib/flow_chat/whatsapp/send_job_support.rb +0 -79
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.9.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:
|
|
11
|
+
date: 2026-07-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: zeitwerk
|
|
@@ -80,6 +80,48 @@ dependencies:
|
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: 0.4.2
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: intercom
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '4.2'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '4.2'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: reverse_markdown
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '3.0'
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '3.0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: kramdown
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '2.4'
|
|
118
|
+
type: :runtime
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '2.4'
|
|
83
125
|
description: "FlowChat is a Rails framework for building sophisticated conversational
|
|
84
126
|
interfaces across USSD and WhatsApp platforms. \nCreate interactive flows with menus,
|
|
85
127
|
prompts, validation, media support, and session management. Features include \nmulti-tenancy,
|
|
@@ -92,9 +134,15 @@ extensions: []
|
|
|
92
134
|
extra_rdoc_files: []
|
|
93
135
|
files:
|
|
94
136
|
- ".DS_Store"
|
|
137
|
+
- ".cliff.toml"
|
|
95
138
|
- ".github/workflows/ci.yml"
|
|
139
|
+
- ".github/workflows/release.yml"
|
|
96
140
|
- ".gitignore"
|
|
97
141
|
- ".ruby-version"
|
|
142
|
+
- ".standard.yml"
|
|
143
|
+
- CHANGELOG.md
|
|
144
|
+
- CLAUDE.md
|
|
145
|
+
- CONTRIBUTING.md
|
|
98
146
|
- Gemfile
|
|
99
147
|
- LICENSE.txt
|
|
100
148
|
- README.md
|
|
@@ -102,63 +150,82 @@ files:
|
|
|
102
150
|
- SECURITY.md
|
|
103
151
|
- bin/console
|
|
104
152
|
- bin/setup
|
|
153
|
+
- docs/architecture.md
|
|
154
|
+
- docs/async-background-processing.md
|
|
105
155
|
- docs/configuration.md
|
|
106
|
-
- docs/
|
|
107
|
-
- docs/
|
|
156
|
+
- docs/factory-pattern.md
|
|
157
|
+
- docs/gateway-context-variables.md
|
|
158
|
+
- docs/gateway-development.md
|
|
159
|
+
- docs/getting-started.md
|
|
108
160
|
- docs/instrumentation.md
|
|
109
|
-
- docs/
|
|
110
|
-
- docs/
|
|
161
|
+
- docs/platforms/telegram.md
|
|
162
|
+
- docs/platforms/ussd.md
|
|
163
|
+
- docs/platforms/whatsapp.md
|
|
111
164
|
- docs/testing.md
|
|
112
|
-
-
|
|
113
|
-
-
|
|
165
|
+
- examples/custom_session_id_example.rb
|
|
166
|
+
- examples/http_controller.rb
|
|
167
|
+
- examples/intercom_configuration_example.rb
|
|
168
|
+
- examples/intercom_controller.rb
|
|
114
169
|
- examples/multi_tenant_whatsapp_controller.rb
|
|
115
170
|
- examples/simulator_controller.rb
|
|
116
171
|
- examples/ussd_controller.rb
|
|
117
172
|
- examples/whatsapp_controller.rb
|
|
118
173
|
- examples/whatsapp_media_examples.rb
|
|
119
|
-
- examples/whatsapp_message_job.rb
|
|
120
174
|
- flow_chat.gemspec
|
|
121
175
|
- lib/flow_chat.rb
|
|
122
|
-
- lib/flow_chat/
|
|
123
|
-
- lib/flow_chat/
|
|
124
|
-
- lib/flow_chat/base_processor.rb
|
|
176
|
+
- lib/flow_chat/app.rb
|
|
177
|
+
- lib/flow_chat/async_job.rb
|
|
125
178
|
- lib/flow_chat/config.rb
|
|
126
179
|
- lib/flow_chat/context.rb
|
|
180
|
+
- lib/flow_chat/executor.rb
|
|
181
|
+
- lib/flow_chat/factory.rb
|
|
127
182
|
- lib/flow_chat/flow.rb
|
|
183
|
+
- lib/flow_chat/gateway_async_support.rb
|
|
184
|
+
- lib/flow_chat/generic_async_job.rb
|
|
185
|
+
- lib/flow_chat/http/gateway/simple.rb
|
|
186
|
+
- lib/flow_chat/http/renderer.rb
|
|
128
187
|
- lib/flow_chat/instrumentation.rb
|
|
129
188
|
- lib/flow_chat/instrumentation/log_subscriber.rb
|
|
130
189
|
- lib/flow_chat/instrumentation/metrics_collector.rb
|
|
131
190
|
- lib/flow_chat/instrumentation/setup.rb
|
|
191
|
+
- lib/flow_chat/intercom/client.rb
|
|
192
|
+
- lib/flow_chat/intercom/configuration.rb
|
|
193
|
+
- lib/flow_chat/intercom/gateway/intercom_api.rb
|
|
194
|
+
- lib/flow_chat/intercom/renderer.rb
|
|
132
195
|
- lib/flow_chat/interrupt.rb
|
|
196
|
+
- lib/flow_chat/phone_number_util.rb
|
|
197
|
+
- lib/flow_chat/processor.rb
|
|
133
198
|
- lib/flow_chat/prompt.rb
|
|
199
|
+
- lib/flow_chat/renderers/markdown_support.rb
|
|
134
200
|
- lib/flow_chat/session/cache_session_store.rb
|
|
135
201
|
- lib/flow_chat/session/middleware.rb
|
|
136
|
-
- lib/flow_chat/session/rails_session_store.rb
|
|
137
202
|
- lib/flow_chat/simulator/controller.rb
|
|
138
203
|
- lib/flow_chat/simulator/views/simulator.html.erb
|
|
139
|
-
- lib/flow_chat/
|
|
204
|
+
- lib/flow_chat/telegram.rb
|
|
205
|
+
- lib/flow_chat/telegram/client.rb
|
|
206
|
+
- lib/flow_chat/telegram/configuration.rb
|
|
207
|
+
- lib/flow_chat/telegram/gateway/bot_api.rb
|
|
208
|
+
- lib/flow_chat/telegram/middleware/choice_mapper.rb
|
|
209
|
+
- lib/flow_chat/telegram/renderer.rb
|
|
140
210
|
- lib/flow_chat/ussd/gateway/nalo.rb
|
|
141
|
-
- lib/flow_chat/ussd/gateway/nsano.rb
|
|
142
211
|
- lib/flow_chat/ussd/middleware/choice_mapper.rb
|
|
143
|
-
- lib/flow_chat/ussd/middleware/executor.rb
|
|
144
212
|
- lib/flow_chat/ussd/middleware/pagination.rb
|
|
145
|
-
- lib/flow_chat/ussd/processor.rb
|
|
146
213
|
- lib/flow_chat/ussd/renderer.rb
|
|
147
214
|
- lib/flow_chat/version.rb
|
|
148
|
-
- lib/flow_chat/whatsapp/app.rb
|
|
149
215
|
- lib/flow_chat/whatsapp/client.rb
|
|
150
216
|
- lib/flow_chat/whatsapp/configuration.rb
|
|
151
217
|
- lib/flow_chat/whatsapp/gateway/cloud_api.rb
|
|
152
|
-
- lib/flow_chat/whatsapp/
|
|
153
|
-
- lib/flow_chat/whatsapp/
|
|
218
|
+
- lib/flow_chat/whatsapp/id_generator.rb
|
|
219
|
+
- lib/flow_chat/whatsapp/middleware/choice_mapper.rb
|
|
154
220
|
- lib/flow_chat/whatsapp/renderer.rb
|
|
155
|
-
- lib/flow_chat/whatsapp/send_job_support.rb
|
|
156
221
|
- lib/flow_chat/whatsapp/template_manager.rb
|
|
222
|
+
- lib/tasks/release.rake
|
|
157
223
|
homepage: https://github.com/radioactive-labs/flow_chat
|
|
158
224
|
licenses:
|
|
159
225
|
- MIT
|
|
160
226
|
metadata:
|
|
161
227
|
allowed_push_host: https://rubygems.org
|
|
228
|
+
rubygems_mfa_required: 'true'
|
|
162
229
|
homepage_uri: https://github.com/radioactive-labs/flow_chat
|
|
163
230
|
source_code_uri: https://github.com/radioactive-labs/flow_chat
|
|
164
231
|
changelog_uri: https://github.com/radioactive-labs/flow_chat
|
data/docs/flows.md
DELETED
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
# Flow Development Guide
|
|
2
|
-
|
|
3
|
-
This guide covers advanced flow patterns, validation techniques, and best practices for building sophisticated conversational workflows.
|
|
4
|
-
|
|
5
|
-
## Flow Architecture
|
|
6
|
-
|
|
7
|
-
### Flow Lifecycle
|
|
8
|
-
|
|
9
|
-
Every flow method must result in user interaction:
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
class ExampleFlow < FlowChat::Flow
|
|
13
|
-
def main_page
|
|
14
|
-
# ✅ Always end with user interaction
|
|
15
|
-
choice = app.screen(:choice) { |p| p.select "Choose:", ["A", "B"] }
|
|
16
|
-
|
|
17
|
-
case choice
|
|
18
|
-
when "A"
|
|
19
|
-
handle_option_a
|
|
20
|
-
app.say "Option A completed!" # Required interaction
|
|
21
|
-
when "B"
|
|
22
|
-
handle_option_b
|
|
23
|
-
app.say "Option B completed!" # Required interaction
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### Session Management
|
|
30
|
-
|
|
31
|
-
FlowChat automatically persists screen results:
|
|
32
|
-
|
|
33
|
-
```ruby
|
|
34
|
-
class RegistrationFlow < FlowChat::Flow
|
|
35
|
-
def main_page
|
|
36
|
-
# These values persist across requests
|
|
37
|
-
name = app.screen(:name) { |p| p.ask "Name?" }
|
|
38
|
-
email = app.screen(:email) { |p| p.ask "Email?" }
|
|
39
|
-
|
|
40
|
-
# Show summary using cached values
|
|
41
|
-
confirmed = app.screen(:confirm) do |prompt|
|
|
42
|
-
prompt.yes? "Create account for #{name} (#{email})?"
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
if confirmed
|
|
46
|
-
create_user(name: name, email: email)
|
|
47
|
-
app.say "Account created!"
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Input Validation Patterns
|
|
54
|
-
|
|
55
|
-
### Basic Validation
|
|
56
|
-
|
|
57
|
-
```ruby
|
|
58
|
-
age = app.screen(:age) do |prompt|
|
|
59
|
-
prompt.ask "Enter your age:",
|
|
60
|
-
validate: ->(input) {
|
|
61
|
-
return "Age must be a number" unless input.match?(/^\d+$/)
|
|
62
|
-
return "Must be 18 or older" unless input.to_i >= 18
|
|
63
|
-
nil # Return nil for valid input
|
|
64
|
-
},
|
|
65
|
-
transform: ->(input) { input.to_i }
|
|
66
|
-
end
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Complex Validation
|
|
70
|
-
|
|
71
|
-
```ruby
|
|
72
|
-
phone = app.screen(:phone) do |prompt|
|
|
73
|
-
prompt.ask "Enter phone number:",
|
|
74
|
-
validate: ->(input) {
|
|
75
|
-
clean = input.gsub(/[\s\-\(\)]/, '')
|
|
76
|
-
return "Invalid format" unless clean.match?(/^\+?[\d]{10,15}$/)
|
|
77
|
-
return "Must start with country code" unless clean.start_with?('+')
|
|
78
|
-
nil
|
|
79
|
-
},
|
|
80
|
-
transform: ->(input) { input.gsub(/[\s\-\(\)]/, '') }
|
|
81
|
-
end
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Conditional Validation
|
|
85
|
-
|
|
86
|
-
```ruby
|
|
87
|
-
class PaymentFlow < FlowChat::Flow
|
|
88
|
-
def collect_payment_method
|
|
89
|
-
method = app.screen(:method) do |prompt|
|
|
90
|
-
prompt.select "Payment method:", ["card", "mobile_money"]
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
if method == "card"
|
|
94
|
-
collect_card_details
|
|
95
|
-
else
|
|
96
|
-
collect_mobile_money_details
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
private
|
|
101
|
-
|
|
102
|
-
def collect_card_details
|
|
103
|
-
card = app.screen(:card) do |prompt|
|
|
104
|
-
prompt.ask "Card number (16 digits):",
|
|
105
|
-
validate: ->(input) {
|
|
106
|
-
clean = input.gsub(/\s/, '')
|
|
107
|
-
return "Must be 16 digits" unless clean.length == 16
|
|
108
|
-
return "Invalid card number" unless luhn_valid?(clean)
|
|
109
|
-
nil
|
|
110
|
-
}
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
app.say "Card ending in #{card[-4..-1]} saved."
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Menu Patterns
|
|
119
|
-
|
|
120
|
-
### Dynamic Menus
|
|
121
|
-
|
|
122
|
-
```ruby
|
|
123
|
-
def show_products
|
|
124
|
-
products = fetch_available_products
|
|
125
|
-
|
|
126
|
-
choice = app.screen(:product) do |prompt|
|
|
127
|
-
prompt.select "Choose product:", products.map(&:name)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
selected_product = products.find { |p| p.name == choice }
|
|
131
|
-
show_product_details(selected_product)
|
|
132
|
-
end
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Nested Menus
|
|
136
|
-
|
|
137
|
-
```ruby
|
|
138
|
-
def main_menu
|
|
139
|
-
choice = app.screen(:main) do |prompt|
|
|
140
|
-
prompt.select "Main Menu:", {
|
|
141
|
-
"products" => "View Products",
|
|
142
|
-
"orders" => "My Orders",
|
|
143
|
-
"support" => "Customer Support"
|
|
144
|
-
}
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
case choice
|
|
148
|
-
when "products"
|
|
149
|
-
products_menu
|
|
150
|
-
when "orders"
|
|
151
|
-
orders_menu
|
|
152
|
-
when "support"
|
|
153
|
-
support_menu
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
## Advanced Patterns
|
|
159
|
-
|
|
160
|
-
### Multi-Step Forms
|
|
161
|
-
|
|
162
|
-
```ruby
|
|
163
|
-
class CompleteProfileFlow < FlowChat::Flow
|
|
164
|
-
def main_page
|
|
165
|
-
collect_basic_info
|
|
166
|
-
collect_preferences
|
|
167
|
-
confirm_and_save
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
private
|
|
171
|
-
|
|
172
|
-
def collect_basic_info
|
|
173
|
-
app.screen(:name) { |p| p.ask "Full name:" }
|
|
174
|
-
app.screen(:email) { |p| p.ask "Email:" }
|
|
175
|
-
app.screen(:phone) { |p| p.ask "Phone:" }
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def collect_preferences
|
|
179
|
-
app.screen(:language) { |p| p.select "Language:", ["English", "French"] }
|
|
180
|
-
app.screen(:notifications) { |p| p.yes? "Enable notifications?" }
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def confirm_and_save
|
|
184
|
-
summary = build_summary
|
|
185
|
-
confirmed = app.screen(:confirm) { |p| p.yes? "Save profile?\n\n#{summary}" }
|
|
186
|
-
|
|
187
|
-
if confirmed
|
|
188
|
-
save_profile
|
|
189
|
-
app.say "Profile saved successfully!"
|
|
190
|
-
else
|
|
191
|
-
app.say "Profile not saved."
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Error Recovery
|
|
198
|
-
|
|
199
|
-
```ruby
|
|
200
|
-
def process_payment
|
|
201
|
-
begin
|
|
202
|
-
amount = app.screen(:amount) do |prompt|
|
|
203
|
-
prompt.ask "Amount to pay:",
|
|
204
|
-
validate: ->(input) {
|
|
205
|
-
return "Invalid amount" unless input.match?(/^\d+(\.\d{2})?$/)
|
|
206
|
-
return "Minimum $1.00" unless input.to_f >= 1.0
|
|
207
|
-
nil
|
|
208
|
-
},
|
|
209
|
-
transform: ->(input) { input.to_f }
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
process_transaction(amount)
|
|
213
|
-
app.say "Payment of $#{amount} processed successfully!"
|
|
214
|
-
|
|
215
|
-
rescue PaymentError => e
|
|
216
|
-
app.say "Payment failed: #{e.message}. Please try again."
|
|
217
|
-
process_payment # Retry
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
## Cross-Platform Considerations
|
|
223
|
-
|
|
224
|
-
### Platform Detection
|
|
225
|
-
|
|
226
|
-
```ruby
|
|
227
|
-
def show_help
|
|
228
|
-
if app.context["request.gateway"] == :whatsapp_cloud_api
|
|
229
|
-
# WhatsApp users get rich media
|
|
230
|
-
app.say "Here's how to use our service:",
|
|
231
|
-
media: { type: :image, url: "https://example.com/help.jpg" }
|
|
232
|
-
else
|
|
233
|
-
# USSD users get text with link
|
|
234
|
-
app.say "Help guide: https://example.com/help"
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Progressive Enhancement
|
|
240
|
-
|
|
241
|
-
```ruby
|
|
242
|
-
def collect_feedback
|
|
243
|
-
rating = app.screen(:rating) do |prompt|
|
|
244
|
-
if whatsapp?
|
|
245
|
-
# Rich interactive buttons for WhatsApp
|
|
246
|
-
prompt.select "Rate our service:", {
|
|
247
|
-
"5" => "⭐⭐⭐⭐⭐ Excellent",
|
|
248
|
-
"4" => "⭐⭐⭐⭐ Good",
|
|
249
|
-
"3" => "⭐⭐⭐ Average",
|
|
250
|
-
"2" => "⭐⭐ Poor",
|
|
251
|
-
"1" => "⭐ Very Poor"
|
|
252
|
-
}
|
|
253
|
-
else
|
|
254
|
-
# Simple numbered list for USSD
|
|
255
|
-
prompt.select "Rate our service (1-5):", ["1", "2", "3", "4", "5"]
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
app.say "Thank you for rating us #{rating} stars!"
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
private
|
|
263
|
-
|
|
264
|
-
def whatsapp?
|
|
265
|
-
app.context["request.gateway"] == :whatsapp_cloud_api
|
|
266
|
-
end
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
## Best Practices
|
|
270
|
-
|
|
271
|
-
### Keep Methods Focused
|
|
272
|
-
|
|
273
|
-
```ruby
|
|
274
|
-
# ✅ Good: Single responsibility
|
|
275
|
-
def collect_contact_info
|
|
276
|
-
name = app.screen(:name) { |p| p.ask "Name:" }
|
|
277
|
-
email = app.screen(:email) { |p| p.ask "Email:" }
|
|
278
|
-
{ name: name, email: email }
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
# ❌ Avoid: Too much in one method
|
|
282
|
-
def handle_everything
|
|
283
|
-
# 50+ lines of mixed logic
|
|
284
|
-
end
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
### Use Meaningful Screen Names
|
|
288
|
-
|
|
289
|
-
```ruby
|
|
290
|
-
# ✅ Good: Descriptive names
|
|
291
|
-
app.screen(:billing_address) { |p| p.ask "Billing address:" }
|
|
292
|
-
app.screen(:confirm_payment) { |p| p.yes? "Confirm $#{amount}?" }
|
|
293
|
-
|
|
294
|
-
# ❌ Avoid: Generic names
|
|
295
|
-
app.screen(:input1) { |p| p.ask "Address:" }
|
|
296
|
-
app.screen(:confirm) { |p| p.yes? "OK?" }
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### Handle Edge Cases
|
|
300
|
-
|
|
301
|
-
```ruby
|
|
302
|
-
def show_order_history
|
|
303
|
-
orders = fetch_user_orders
|
|
304
|
-
|
|
305
|
-
if orders.empty?
|
|
306
|
-
app.say "You have no previous orders."
|
|
307
|
-
return
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
choice = app.screen(:order) do |prompt|
|
|
311
|
-
prompt.select "Select order:", orders.map(&:display_name)
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
show_order_details(orders.find { |o| o.display_name == choice })
|
|
315
|
-
end
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
## Testing Flows
|
|
319
|
-
|
|
320
|
-
See [Testing Guide](testing.md) for comprehensive testing strategies.
|
data/docs/images/simulator.png
DELETED
|
Binary file
|
data/docs/media.md
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
# Media Support
|
|
2
|
-
|
|
3
|
-
FlowChat supports rich media attachments for enhanced conversational experiences with automatic cross-platform optimization.
|
|
4
|
-
|
|
5
|
-
## Supported Media Types
|
|
6
|
-
|
|
7
|
-
- **📷 Images** (`type: :image`) - Photos, screenshots, diagrams
|
|
8
|
-
- **📄 Documents** (`type: :document`) - PDFs, forms, receipts
|
|
9
|
-
- **🎥 Videos** (`type: :video`) - Tutorials, demos, explanations
|
|
10
|
-
- **🎵 Audio** (`type: :audio`) - Voice messages, recordings
|
|
11
|
-
- **😊 Stickers** (`type: :sticker`) - Fun visual elements
|
|
12
|
-
|
|
13
|
-
## Basic Usage
|
|
14
|
-
|
|
15
|
-
```ruby
|
|
16
|
-
class ProductFlow < FlowChat::Flow
|
|
17
|
-
def main_page
|
|
18
|
-
# Text input with context image
|
|
19
|
-
feedback = app.screen(:feedback) do |prompt|
|
|
20
|
-
prompt.ask "What do you think of our new product?",
|
|
21
|
-
media: {
|
|
22
|
-
type: :image,
|
|
23
|
-
url: "https://cdn.example.com/products/new_product.jpg"
|
|
24
|
-
}
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Send informational media
|
|
28
|
-
app.say "Thanks for your feedback! Here's what's coming next:",
|
|
29
|
-
media: {
|
|
30
|
-
type: :video,
|
|
31
|
-
url: "https://videos.example.com/roadmap.mp4"
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
# Document with filename
|
|
35
|
-
app.say "Here's your receipt:",
|
|
36
|
-
media: {
|
|
37
|
-
type: :document,
|
|
38
|
-
url: "https://api.example.com/receipt.pdf",
|
|
39
|
-
filename: "receipt.pdf"
|
|
40
|
-
}
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Media Hash Format
|
|
46
|
-
|
|
47
|
-
```ruby
|
|
48
|
-
{
|
|
49
|
-
type: :image, # Required: :image, :document, :audio, :video, :sticker
|
|
50
|
-
url: "https://...", # Required: URL to the media file OR WhatsApp media ID
|
|
51
|
-
filename: "doc.pdf" # Optional: Only for documents
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Using WhatsApp Media IDs
|
|
56
|
-
|
|
57
|
-
For better performance and to avoid external dependencies, you can upload files to WhatsApp and use the media ID:
|
|
58
|
-
|
|
59
|
-
```ruby
|
|
60
|
-
# Upload a file first
|
|
61
|
-
client = FlowChat::Whatsapp::Client.new(config)
|
|
62
|
-
media_id = client.upload_media('path/to/image.jpg', 'image/jpeg')
|
|
63
|
-
|
|
64
|
-
# Then use the media ID in your flow
|
|
65
|
-
app.screen(:product_demo) do |prompt|
|
|
66
|
-
prompt.ask "What do you think?",
|
|
67
|
-
media: {
|
|
68
|
-
type: :image,
|
|
69
|
-
url: media_id # Use the media ID instead of URL
|
|
70
|
-
}
|
|
71
|
-
end
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## WhatsApp Client Media Methods
|
|
75
|
-
|
|
76
|
-
The WhatsApp client provides methods for uploading and sending media:
|
|
77
|
-
|
|
78
|
-
```ruby
|
|
79
|
-
client = FlowChat::Whatsapp::Client.new(config)
|
|
80
|
-
|
|
81
|
-
# Upload media and get media ID
|
|
82
|
-
media_id = client.upload_media('image.jpg', 'image/jpeg')
|
|
83
|
-
media_id = client.upload_media(file_io, 'image/jpeg', 'photo.jpg')
|
|
84
|
-
|
|
85
|
-
# Send media directly
|
|
86
|
-
client.send_image("+1234567890", "https://example.com/image.jpg", "Caption")
|
|
87
|
-
client.send_image("+1234567890", media_id, "Caption")
|
|
88
|
-
|
|
89
|
-
# Send document with MIME type and filename
|
|
90
|
-
client.send_document("+1234567890", "https://example.com/doc.pdf", "Your receipt", "receipt.pdf", "application/pdf")
|
|
91
|
-
|
|
92
|
-
# Send other media types
|
|
93
|
-
client.send_video("+1234567890", "https://example.com/video.mp4", "Demo video", "video/mp4")
|
|
94
|
-
client.send_audio("+1234567890", "https://example.com/audio.mp3", "audio/mpeg")
|
|
95
|
-
client.send_sticker("+1234567890", "https://example.com/sticker.webp", "image/webp")
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Cross-Platform Behavior
|
|
99
|
-
|
|
100
|
-
### WhatsApp Experience
|
|
101
|
-
- Media is sent directly to the chat
|
|
102
|
-
- Prompt text becomes the media caption
|
|
103
|
-
- Rich, native messaging experience
|
|
104
|
-
|
|
105
|
-
### USSD Experience
|
|
106
|
-
- Media URL is included in text message
|
|
107
|
-
- Graceful degradation with clear media indicators
|
|
108
|
-
- Users can access media via the provided link
|
|
109
|
-
|
|
110
|
-
```ruby
|
|
111
|
-
# This code works on both platforms:
|
|
112
|
-
app.screen(:help) do |prompt|
|
|
113
|
-
prompt.ask "Describe your issue:",
|
|
114
|
-
media: {
|
|
115
|
-
type: :image,
|
|
116
|
-
url: "https://support.example.com/help_example.jpg"
|
|
117
|
-
}
|
|
118
|
-
end
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
**WhatsApp Result:** Image sent with caption "Describe your issue:"
|
|
122
|
-
|
|
123
|
-
**USSD Result:**
|
|
124
|
-
```
|
|
125
|
-
📷 Image: https://support.example.com/help_example.jpg
|
|
126
|
-
|
|
127
|
-
Describe your issue:
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## Advanced Examples
|
|
131
|
-
|
|
132
|
-
### Dynamic Media Based on User Context
|
|
133
|
-
|
|
134
|
-
```ruby
|
|
135
|
-
class SupportFlow < FlowChat::Flow
|
|
136
|
-
def show_help
|
|
137
|
-
user_level = determine_user_level
|
|
138
|
-
|
|
139
|
-
help_media = case user_level
|
|
140
|
-
when :beginner
|
|
141
|
-
{ type: :video, url: "https://help.example.com/beginner_guide.mp4" }
|
|
142
|
-
when :advanced
|
|
143
|
-
{ type: :document, url: "https://help.example.com/advanced_manual.pdf", filename: "advanced_manual.pdf" }
|
|
144
|
-
else
|
|
145
|
-
{ type: :image, url: "https://help.example.com/quick_reference.jpg" }
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
app.say "Here's help tailored for you:", media: help_media
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
See [examples/whatsapp_media_examples.rb](../examples/whatsapp_media_examples.rb) for complete media implementation examples.
|