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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +44 -0
  3. data/.gitignore +2 -1
  4. data/README.md +85 -1229
  5. data/docs/configuration.md +360 -0
  6. data/docs/flows.md +320 -0
  7. data/docs/images/simulator.png +0 -0
  8. data/docs/instrumentation.md +216 -0
  9. data/docs/media.md +153 -0
  10. data/docs/sessions.md +433 -0
  11. data/docs/testing.md +475 -0
  12. data/docs/ussd-setup.md +322 -0
  13. data/docs/whatsapp-setup.md +162 -0
  14. data/examples/multi_tenant_whatsapp_controller.rb +9 -37
  15. data/examples/simulator_controller.rb +13 -22
  16. data/examples/ussd_controller.rb +41 -41
  17. data/examples/whatsapp_controller.rb +32 -125
  18. data/examples/whatsapp_media_examples.rb +68 -336
  19. data/examples/whatsapp_message_job.rb +5 -3
  20. data/flow_chat.gemspec +6 -2
  21. data/lib/flow_chat/base_processor.rb +79 -2
  22. data/lib/flow_chat/config.rb +31 -5
  23. data/lib/flow_chat/context.rb +13 -1
  24. data/lib/flow_chat/instrumentation/log_subscriber.rb +176 -0
  25. data/lib/flow_chat/instrumentation/metrics_collector.rb +197 -0
  26. data/lib/flow_chat/instrumentation/setup.rb +155 -0
  27. data/lib/flow_chat/instrumentation.rb +70 -0
  28. data/lib/flow_chat/prompt.rb +20 -20
  29. data/lib/flow_chat/session/cache_session_store.rb +73 -7
  30. data/lib/flow_chat/session/middleware.rb +130 -12
  31. data/lib/flow_chat/session/rails_session_store.rb +36 -1
  32. data/lib/flow_chat/simulator/controller.rb +8 -8
  33. data/lib/flow_chat/simulator/views/simulator.html.erb +5 -5
  34. data/lib/flow_chat/ussd/gateway/nalo.rb +31 -0
  35. data/lib/flow_chat/ussd/gateway/nsano.rb +36 -2
  36. data/lib/flow_chat/ussd/middleware/choice_mapper.rb +109 -0
  37. data/lib/flow_chat/ussd/middleware/executor.rb +24 -2
  38. data/lib/flow_chat/ussd/middleware/pagination.rb +87 -7
  39. data/lib/flow_chat/ussd/processor.rb +16 -4
  40. data/lib/flow_chat/ussd/renderer.rb +1 -1
  41. data/lib/flow_chat/version.rb +1 -1
  42. data/lib/flow_chat/whatsapp/client.rb +99 -12
  43. data/lib/flow_chat/whatsapp/configuration.rb +35 -4
  44. data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +121 -34
  45. data/lib/flow_chat/whatsapp/middleware/executor.rb +24 -2
  46. data/lib/flow_chat/whatsapp/processor.rb +7 -1
  47. data/lib/flow_chat/whatsapp/renderer.rb +4 -9
  48. data/lib/flow_chat.rb +23 -0
  49. metadata +23 -12
  50. data/.travis.yml +0 -6
  51. data/app/controllers/demo_controller.rb +0 -101
  52. data/app/flow_chat/demo_restaurant_flow.rb +0 -889
  53. data/config/routes_demo.rb +0 -59
  54. data/examples/initializer.rb +0 -86
  55. data/examples/media_prompts_examples.rb +0 -27
  56. data/images/ussd_simulator.png +0 -0
  57. 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
- flow = context.flow.new whatsapp_app
12
- flow.send context["flow.action"]
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
- builder.use FlowChat::Session::Middleware
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
- # Determine the best way to present choices
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 an Array or Hash"
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.6.1
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-05 00:00:00.000000000 Z
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: Framework for building Menu based conversations (e.g. USSD) in Rails.
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
- - config/routes_demo.rb
104
- - examples/initializer.rb
105
- - examples/media_prompts_examples.rb
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: Framework for building Menu based conversations (e.g. USSD) in Rails.
181
+ summary: Build conversational interfaces for USSD and WhatsApp with Rails
171
182
  test_files: []
data/.travis.yml DELETED
@@ -1,6 +0,0 @@
1
- ---
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.7.1
6
- before_install: gem install bundler -v 2.1.4
@@ -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