flow_chat 0.6.1 → 0.8.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/.github/workflows/ci.yml +44 -0
- data/.gitignore +2 -1
- data/README.md +85 -1229
- data/docs/configuration.md +360 -0
- data/docs/flows.md +320 -0
- data/docs/images/simulator.png +0 -0
- data/docs/instrumentation.md +216 -0
- data/docs/media.md +153 -0
- data/docs/sessions.md +433 -0
- data/docs/testing.md +475 -0
- data/docs/ussd-setup.md +322 -0
- data/docs/whatsapp-setup.md +162 -0
- data/examples/multi_tenant_whatsapp_controller.rb +9 -37
- data/examples/simulator_controller.rb +13 -22
- data/examples/ussd_controller.rb +41 -41
- data/examples/whatsapp_controller.rb +32 -125
- data/examples/whatsapp_media_examples.rb +68 -336
- data/examples/whatsapp_message_job.rb +5 -3
- data/flow_chat.gemspec +6 -2
- data/lib/flow_chat/base_processor.rb +79 -2
- data/lib/flow_chat/config.rb +31 -5
- data/lib/flow_chat/context.rb +13 -1
- data/lib/flow_chat/instrumentation/log_subscriber.rb +176 -0
- data/lib/flow_chat/instrumentation/metrics_collector.rb +197 -0
- data/lib/flow_chat/instrumentation/setup.rb +155 -0
- data/lib/flow_chat/instrumentation.rb +70 -0
- data/lib/flow_chat/prompt.rb +20 -20
- data/lib/flow_chat/session/cache_session_store.rb +73 -7
- data/lib/flow_chat/session/middleware.rb +130 -12
- data/lib/flow_chat/session/rails_session_store.rb +36 -1
- data/lib/flow_chat/simulator/controller.rb +8 -8
- data/lib/flow_chat/simulator/views/simulator.html.erb +5 -5
- data/lib/flow_chat/ussd/gateway/nalo.rb +31 -0
- data/lib/flow_chat/ussd/gateway/nsano.rb +36 -2
- data/lib/flow_chat/ussd/middleware/choice_mapper.rb +109 -0
- data/lib/flow_chat/ussd/middleware/executor.rb +24 -2
- data/lib/flow_chat/ussd/middleware/pagination.rb +87 -7
- data/lib/flow_chat/ussd/processor.rb +16 -4
- data/lib/flow_chat/ussd/renderer.rb +1 -1
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/client.rb +99 -12
- data/lib/flow_chat/whatsapp/configuration.rb +35 -4
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +121 -34
- data/lib/flow_chat/whatsapp/middleware/executor.rb +24 -2
- data/lib/flow_chat/whatsapp/processor.rb +7 -1
- data/lib/flow_chat/whatsapp/renderer.rb +4 -9
- data/lib/flow_chat.rb +23 -0
- metadata +23 -12
- data/.travis.yml +0 -6
- data/app/controllers/demo_controller.rb +0 -101
- data/app/flow_chat/demo_restaurant_flow.rb +0 -889
- data/config/routes_demo.rb +0 -59
- data/examples/initializer.rb +0 -86
- data/examples/media_prompts_examples.rb +0 -27
- data/images/ussd_simulator.png +0 -0
- data/lib/flow_chat/ussd/middleware/resumable_session.rb +0 -39
|
@@ -4,24 +4,46 @@ module FlowChat
|
|
|
4
4
|
class Executor
|
|
5
5
|
def initialize(app)
|
|
6
6
|
@app = app
|
|
7
|
+
FlowChat.logger.debug { "Whatsapp::Executor: Initialized WhatsApp executor middleware" }
|
|
7
8
|
end
|
|
8
9
|
|
|
9
10
|
def call(context)
|
|
11
|
+
flow_class = context.flow
|
|
12
|
+
action = context["flow.action"]
|
|
13
|
+
session_id = context["session.id"]
|
|
14
|
+
|
|
15
|
+
FlowChat.logger.info { "Whatsapp::Executor: Executing flow #{flow_class.name}##{action} for session #{session_id}" }
|
|
16
|
+
|
|
10
17
|
whatsapp_app = build_whatsapp_app context
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
FlowChat.logger.debug { "Whatsapp::Executor: WhatsApp app built for flow execution" }
|
|
19
|
+
|
|
20
|
+
flow = flow_class.new whatsapp_app
|
|
21
|
+
FlowChat.logger.debug { "Whatsapp::Executor: Flow instance created, invoking #{action} method" }
|
|
22
|
+
|
|
23
|
+
flow.send action
|
|
24
|
+
FlowChat.logger.warn { "Whatsapp::Executor: Flow execution failed to interact with user for #{flow_class.name}##{action}" }
|
|
25
|
+
raise FlowChat::Interrupt::Terminate, "Unexpected end of flow."
|
|
13
26
|
rescue FlowChat::Interrupt::Prompt => e
|
|
27
|
+
FlowChat.logger.info { "Whatsapp::Executor: Flow prompted user - Session: #{session_id}, Prompt: '#{e.prompt.truncate(100)}'" }
|
|
28
|
+
FlowChat.logger.debug { "Whatsapp::Executor: Prompt details - Choices: #{e.choices&.size || 0}, Has media: #{!e.media.nil?}" }
|
|
14
29
|
# Return the same triplet format as USSD for consistency
|
|
15
30
|
[:prompt, e.prompt, e.choices, e.media]
|
|
16
31
|
rescue FlowChat::Interrupt::Terminate => e
|
|
32
|
+
FlowChat.logger.info { "Whatsapp::Executor: Flow terminated - Session: #{session_id}, Message: '#{e.prompt.truncate(100)}'" }
|
|
33
|
+
FlowChat.logger.debug { "Whatsapp::Executor: Destroying session #{session_id}" }
|
|
17
34
|
# Clean up session and return terminal message
|
|
18
35
|
context.session.destroy
|
|
19
36
|
[:terminate, e.prompt, nil, e.media]
|
|
37
|
+
rescue => error
|
|
38
|
+
FlowChat.logger.error { "Whatsapp::Executor: Flow execution failed - #{flow_class.name}##{action}, Session: #{session_id}, Error: #{error.class.name}: #{error.message}" }
|
|
39
|
+
FlowChat.logger.debug { "Whatsapp::Executor: Stack trace: #{error.backtrace.join("\n")}" }
|
|
40
|
+
raise
|
|
20
41
|
end
|
|
21
42
|
|
|
22
43
|
private
|
|
23
44
|
|
|
24
45
|
def build_whatsapp_app(context)
|
|
46
|
+
FlowChat.logger.debug { "Whatsapp::Executor: Building WhatsApp app instance" }
|
|
25
47
|
FlowChat::Whatsapp::App.new(context)
|
|
26
48
|
end
|
|
27
49
|
end
|
|
@@ -2,6 +2,7 @@ module FlowChat
|
|
|
2
2
|
module Whatsapp
|
|
3
3
|
class Processor < FlowChat::BaseProcessor
|
|
4
4
|
def use_whatsapp_config(config)
|
|
5
|
+
FlowChat.logger.debug { "Whatsapp::Processor: Configuring WhatsApp config: #{config.class.name}" }
|
|
5
6
|
@whatsapp_config = config
|
|
6
7
|
self
|
|
7
8
|
end
|
|
@@ -13,13 +14,18 @@ module FlowChat
|
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def build_middleware_stack
|
|
17
|
+
FlowChat.logger.debug { "Whatsapp::Processor: Building WhatsApp middleware stack" }
|
|
16
18
|
create_middleware_stack("whatsapp")
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def configure_middleware_stack(builder)
|
|
20
|
-
|
|
22
|
+
FlowChat.logger.debug { "Whatsapp::Processor: Configuring WhatsApp middleware stack" }
|
|
23
|
+
|
|
21
24
|
builder.use middleware
|
|
25
|
+
FlowChat.logger.debug { "Whatsapp::Processor: Added custom middleware" }
|
|
26
|
+
|
|
22
27
|
builder.use FlowChat::Whatsapp::Middleware::Executor
|
|
28
|
+
FlowChat.logger.debug { "Whatsapp::Processor: Added Whatsapp::Middleware::Executor" }
|
|
23
29
|
end
|
|
24
30
|
end
|
|
25
31
|
end
|
|
@@ -49,15 +49,10 @@ module FlowChat
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def build_selection_message
|
|
52
|
-
|
|
53
|
-
if choices.is_a?(Array)
|
|
54
|
-
# Convert array to hash with index-based keys
|
|
55
|
-
choice_hash = choices.each_with_index.to_h { |choice, index| [index.to_s, choice] }
|
|
56
|
-
build_interactive_message(choice_hash)
|
|
57
|
-
elsif choices.is_a?(Hash)
|
|
52
|
+
if choices.is_a?(Hash)
|
|
58
53
|
build_interactive_message(choices)
|
|
59
54
|
else
|
|
60
|
-
raise ArgumentError, "choices must be
|
|
55
|
+
raise ArgumentError, "choices must be a Hash"
|
|
61
56
|
end
|
|
62
57
|
end
|
|
63
58
|
|
|
@@ -121,7 +116,7 @@ module FlowChat
|
|
|
121
116
|
}
|
|
122
117
|
when :video
|
|
123
118
|
{
|
|
124
|
-
type: "video",
|
|
119
|
+
type: "video",
|
|
125
120
|
video: {link: url}
|
|
126
121
|
}
|
|
127
122
|
when :document
|
|
@@ -188,4 +183,4 @@ module FlowChat
|
|
|
188
183
|
end
|
|
189
184
|
end
|
|
190
185
|
end
|
|
191
|
-
end
|
|
186
|
+
end
|
data/lib/flow_chat.rb
CHANGED
|
@@ -2,6 +2,8 @@ require "zeitwerk"
|
|
|
2
2
|
require "active_support"
|
|
3
3
|
require "active_support/core_ext/time"
|
|
4
4
|
require "active_support/core_ext/object/blank"
|
|
5
|
+
require "active_support/core_ext/string/filters"
|
|
6
|
+
require "active_support/core_ext/enumerable"
|
|
5
7
|
|
|
6
8
|
loader = Zeitwerk::Loader.for_gem
|
|
7
9
|
loader.enable_reloading if defined?(Rails.env) && Rails.env.development?
|
|
@@ -11,6 +13,27 @@ module FlowChat
|
|
|
11
13
|
def self.root
|
|
12
14
|
Pathname.new __dir__
|
|
13
15
|
end
|
|
16
|
+
|
|
17
|
+
def self.setup_instrumentation!
|
|
18
|
+
require_relative "flow_chat/instrumentation/setup"
|
|
19
|
+
FlowChat::Instrumentation::Setup.setup_instrumentation!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Access to instrumentation
|
|
23
|
+
def self.instrument(event_name, payload = {}, &block)
|
|
24
|
+
FlowChat::Instrumentation.instrument(event_name, payload, &block)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.metrics
|
|
28
|
+
FlowChat::Instrumentation::Setup.metrics_collector
|
|
29
|
+
end
|
|
14
30
|
end
|
|
15
31
|
|
|
16
32
|
loader.eager_load
|
|
33
|
+
|
|
34
|
+
# Auto-setup instrumentation in Rails environments
|
|
35
|
+
if defined?(Rails)
|
|
36
|
+
Rails.application.config.after_initialize do
|
|
37
|
+
FlowChat.setup_instrumentation!
|
|
38
|
+
end
|
|
39
|
+
end
|
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.8.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: 2025-06-
|
|
11
|
+
date: 2025-06-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: zeitwerk
|
|
@@ -80,7 +80,11 @@ dependencies:
|
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: 0.4.2
|
|
83
|
-
description:
|
|
83
|
+
description: "FlowChat is a Rails framework for building sophisticated conversational
|
|
84
|
+
interfaces across USSD and WhatsApp platforms. \nCreate interactive flows with menus,
|
|
85
|
+
prompts, validation, media support, and session management. Features include \nmulti-tenancy,
|
|
86
|
+
background job processing, built-in simulator for testing, and comprehensive middleware
|
|
87
|
+
support.\n"
|
|
84
88
|
email:
|
|
85
89
|
- sfroelich01@gmail.com
|
|
86
90
|
executables: []
|
|
@@ -88,21 +92,25 @@ extensions: []
|
|
|
88
92
|
extra_rdoc_files: []
|
|
89
93
|
files:
|
|
90
94
|
- ".DS_Store"
|
|
95
|
+
- ".github/workflows/ci.yml"
|
|
91
96
|
- ".gitignore"
|
|
92
97
|
- ".ruby-version"
|
|
93
|
-
- ".travis.yml"
|
|
94
98
|
- Gemfile
|
|
95
99
|
- LICENSE.txt
|
|
96
100
|
- README.md
|
|
97
101
|
- Rakefile
|
|
98
102
|
- SECURITY.md
|
|
99
|
-
- app/controllers/demo_controller.rb
|
|
100
|
-
- app/flow_chat/demo_restaurant_flow.rb
|
|
101
103
|
- bin/console
|
|
102
104
|
- bin/setup
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
105
|
+
- docs/configuration.md
|
|
106
|
+
- docs/flows.md
|
|
107
|
+
- docs/images/simulator.png
|
|
108
|
+
- docs/instrumentation.md
|
|
109
|
+
- docs/media.md
|
|
110
|
+
- docs/sessions.md
|
|
111
|
+
- docs/testing.md
|
|
112
|
+
- docs/ussd-setup.md
|
|
113
|
+
- docs/whatsapp-setup.md
|
|
106
114
|
- examples/multi_tenant_whatsapp_controller.rb
|
|
107
115
|
- examples/simulator_controller.rb
|
|
108
116
|
- examples/ussd_controller.rb
|
|
@@ -110,12 +118,15 @@ files:
|
|
|
110
118
|
- examples/whatsapp_media_examples.rb
|
|
111
119
|
- examples/whatsapp_message_job.rb
|
|
112
120
|
- flow_chat.gemspec
|
|
113
|
-
- images/ussd_simulator.png
|
|
114
121
|
- lib/flow_chat.rb
|
|
115
122
|
- lib/flow_chat/base_processor.rb
|
|
116
123
|
- lib/flow_chat/config.rb
|
|
117
124
|
- lib/flow_chat/context.rb
|
|
118
125
|
- lib/flow_chat/flow.rb
|
|
126
|
+
- lib/flow_chat/instrumentation.rb
|
|
127
|
+
- lib/flow_chat/instrumentation/log_subscriber.rb
|
|
128
|
+
- lib/flow_chat/instrumentation/metrics_collector.rb
|
|
129
|
+
- lib/flow_chat/instrumentation/setup.rb
|
|
119
130
|
- lib/flow_chat/interrupt.rb
|
|
120
131
|
- lib/flow_chat/prompt.rb
|
|
121
132
|
- lib/flow_chat/session/cache_session_store.rb
|
|
@@ -126,9 +137,9 @@ files:
|
|
|
126
137
|
- lib/flow_chat/ussd/app.rb
|
|
127
138
|
- lib/flow_chat/ussd/gateway/nalo.rb
|
|
128
139
|
- lib/flow_chat/ussd/gateway/nsano.rb
|
|
140
|
+
- lib/flow_chat/ussd/middleware/choice_mapper.rb
|
|
129
141
|
- lib/flow_chat/ussd/middleware/executor.rb
|
|
130
142
|
- lib/flow_chat/ussd/middleware/pagination.rb
|
|
131
|
-
- lib/flow_chat/ussd/middleware/resumable_session.rb
|
|
132
143
|
- lib/flow_chat/ussd/processor.rb
|
|
133
144
|
- lib/flow_chat/ussd/renderer.rb
|
|
134
145
|
- lib/flow_chat/version.rb
|
|
@@ -167,5 +178,5 @@ requirements: []
|
|
|
167
178
|
rubygems_version: 3.4.10
|
|
168
179
|
signing_key:
|
|
169
180
|
specification_version: 4
|
|
170
|
-
summary:
|
|
181
|
+
summary: Build conversational interfaces for USSD and WhatsApp with Rails
|
|
171
182
|
test_files: []
|
data/.travis.yml
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# Demo Controller - Showcases FlowChat comprehensive features
|
|
2
|
-
#
|
|
3
|
-
# This controller demonstrates how to use the DemoRestaurantFlow
|
|
4
|
-
# across both USSD and WhatsApp platforms, showing off all FlowChat features.
|
|
5
|
-
#
|
|
6
|
-
# Features demonstrated:
|
|
7
|
-
# - Cross-platform compatibility
|
|
8
|
-
# - Media support with graceful degradation
|
|
9
|
-
# - Complex workflows with session management
|
|
10
|
-
# - Input validation and transformation
|
|
11
|
-
# - Rich interactive elements
|
|
12
|
-
|
|
13
|
-
class DemoController < ApplicationController
|
|
14
|
-
skip_forgery_protection
|
|
15
|
-
|
|
16
|
-
# USSD Demo Endpoint
|
|
17
|
-
# Usage: POST /demo/ussd
|
|
18
|
-
def ussd_demo
|
|
19
|
-
processor = FlowChat::Ussd::Processor.new(self) do |config|
|
|
20
|
-
config.use_gateway FlowChat::Ussd::Gateway::Nalo
|
|
21
|
-
config.use_session_store FlowChat::Session::RailsSessionStore
|
|
22
|
-
|
|
23
|
-
# Optional: Enable resumable sessions for better UX
|
|
24
|
-
config.use_resumable_sessions
|
|
25
|
-
|
|
26
|
-
# Optional: Custom pagination settings for large menus
|
|
27
|
-
FlowChat::Config.ussd.pagination_page_size = 120 # Slightly larger for demo
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
processor.run DemoRestaurantFlow, :main_page
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# WhatsApp Demo Endpoint
|
|
34
|
-
# Usage: GET/POST /demo/whatsapp
|
|
35
|
-
def whatsapp_demo
|
|
36
|
-
processor = FlowChat::Whatsapp::Processor.new(self, enable_simulator: Rails.env.development?) do |config|
|
|
37
|
-
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
38
|
-
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
processor.run DemoRestaurantFlow, :main_page
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Alternative WhatsApp Demo with Custom Configuration
|
|
45
|
-
# Usage: GET/POST /demo/whatsapp_custom
|
|
46
|
-
def whatsapp_custom_demo
|
|
47
|
-
# Custom configuration for multi-tenant demo
|
|
48
|
-
custom_config = FlowChat::Whatsapp::Configuration.new
|
|
49
|
-
custom_config.access_token = ENV['DEMO_WHATSAPP_ACCESS_TOKEN']
|
|
50
|
-
custom_config.phone_number_id = ENV['DEMO_WHATSAPP_PHONE_NUMBER_ID']
|
|
51
|
-
custom_config.verify_token = ENV['DEMO_WHATSAPP_VERIFY_TOKEN']
|
|
52
|
-
custom_config.app_secret = ENV['DEMO_WHATSAPP_APP_SECRET']
|
|
53
|
-
custom_config.skip_signature_validation = Rails.env.development?
|
|
54
|
-
|
|
55
|
-
processor = FlowChat::Whatsapp::Processor.new(self, enable_simulator: true) do |config|
|
|
56
|
-
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, custom_config
|
|
57
|
-
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
processor.run DemoRestaurantFlow, :main_page
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Background Mode Demo
|
|
64
|
-
# Usage: GET/POST /demo/whatsapp_background
|
|
65
|
-
def whatsapp_background_demo
|
|
66
|
-
# Configure for background processing
|
|
67
|
-
original_mode = FlowChat::Config.whatsapp.message_handling_mode
|
|
68
|
-
FlowChat::Config.whatsapp.message_handling_mode = :background
|
|
69
|
-
FlowChat::Config.whatsapp.background_job_class = 'DemoWhatsappJob'
|
|
70
|
-
|
|
71
|
-
processor = FlowChat::Whatsapp::Processor.new(self) do |config|
|
|
72
|
-
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
73
|
-
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
processor.run DemoRestaurantFlow, :main_page
|
|
77
|
-
|
|
78
|
-
ensure
|
|
79
|
-
# Restore original mode
|
|
80
|
-
FlowChat::Config.whatsapp.message_handling_mode = original_mode
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Simulator Mode Demo (for testing)
|
|
84
|
-
# Usage: GET/POST /demo/whatsapp_simulator
|
|
85
|
-
def whatsapp_simulator_demo
|
|
86
|
-
# Force simulator mode for testing
|
|
87
|
-
original_mode = FlowChat::Config.whatsapp.message_handling_mode
|
|
88
|
-
FlowChat::Config.whatsapp.message_handling_mode = :simulator
|
|
89
|
-
|
|
90
|
-
processor = FlowChat::Whatsapp::Processor.new(self, enable_simulator: true) do |config|
|
|
91
|
-
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
92
|
-
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
processor.run DemoRestaurantFlow, :main_page
|
|
96
|
-
|
|
97
|
-
ensure
|
|
98
|
-
# Restore original mode
|
|
99
|
-
FlowChat::Config.whatsapp.message_handling_mode = original_mode
|
|
100
|
-
end
|
|
101
|
-
end
|