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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.cliff.toml +74 -0
  3. data/.github/workflows/ci.yml +2 -3
  4. data/.github/workflows/release.yml +56 -0
  5. data/.standard.yml +4 -0
  6. data/CHANGELOG.md +22 -0
  7. data/CLAUDE.md +327 -0
  8. data/CONTRIBUTING.md +134 -0
  9. data/Gemfile +1 -0
  10. data/README.md +290 -105
  11. data/Rakefile +5 -1
  12. data/SECURITY.md +42 -349
  13. data/docs/architecture.md +510 -0
  14. data/docs/async-background-processing.md +298 -0
  15. data/docs/configuration.md +556 -226
  16. data/docs/factory-pattern.md +355 -0
  17. data/docs/gateway-context-variables.md +171 -0
  18. data/docs/gateway-development.md +723 -0
  19. data/docs/getting-started.md +429 -0
  20. data/docs/instrumentation.md +264 -153
  21. data/docs/platforms/telegram.md +1013 -0
  22. data/docs/platforms/ussd.md +693 -0
  23. data/docs/platforms/whatsapp.md +1395 -0
  24. data/docs/testing.md +243 -365
  25. data/examples/custom_session_id_example.rb +119 -0
  26. data/examples/http_controller.rb +154 -0
  27. data/examples/intercom_configuration_example.rb +118 -0
  28. data/examples/intercom_controller.rb +194 -0
  29. data/examples/multi_tenant_whatsapp_controller.rb +4 -4
  30. data/examples/simulator_controller.rb +21 -1
  31. data/examples/ussd_controller.rb +10 -10
  32. data/examples/whatsapp_controller.rb +2 -2
  33. data/flow_chat.gemspec +4 -0
  34. data/lib/flow_chat/{base_app.rb → app.rb} +28 -9
  35. data/lib/flow_chat/async_job.rb +176 -0
  36. data/lib/flow_chat/config.rb +23 -28
  37. data/lib/flow_chat/{base_executor.rb → executor.rb} +7 -12
  38. data/lib/flow_chat/factory.rb +94 -0
  39. data/lib/flow_chat/gateway_async_support.rb +106 -0
  40. data/lib/flow_chat/generic_async_job.rb +30 -0
  41. data/lib/flow_chat/http/gateway/simple.rb +125 -0
  42. data/lib/flow_chat/http/renderer.rb +41 -0
  43. data/lib/flow_chat/instrumentation/setup.rb +1 -1
  44. data/lib/flow_chat/instrumentation.rb +25 -0
  45. data/lib/flow_chat/intercom/client.rb +155 -0
  46. data/lib/flow_chat/intercom/configuration.rb +149 -0
  47. data/lib/flow_chat/intercom/gateway/intercom_api.rb +396 -0
  48. data/lib/flow_chat/intercom/renderer.rb +71 -0
  49. data/lib/flow_chat/phone_number_util.rb +49 -0
  50. data/lib/flow_chat/processor.rb +188 -0
  51. data/lib/flow_chat/renderers/markdown_support.rb +58 -0
  52. data/lib/flow_chat/session/cache_session_store.rb +1 -17
  53. data/lib/flow_chat/session/middleware.rb +43 -26
  54. data/lib/flow_chat/simulator/controller.rb +17 -5
  55. data/lib/flow_chat/simulator/views/simulator.html.erb +220 -8
  56. data/lib/flow_chat/telegram/client.rb +240 -0
  57. data/lib/flow_chat/telegram/configuration.rb +118 -0
  58. data/lib/flow_chat/telegram/gateway/bot_api.rb +300 -0
  59. data/lib/flow_chat/telegram/middleware/choice_mapper.rb +35 -0
  60. data/lib/flow_chat/telegram/renderer.rb +125 -0
  61. data/lib/flow_chat/telegram.rb +7 -0
  62. data/lib/flow_chat/ussd/gateway/nalo.rb +27 -11
  63. data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
  64. data/lib/flow_chat/ussd/renderer.rb +1 -1
  65. data/lib/flow_chat/version.rb +1 -1
  66. data/lib/flow_chat/whatsapp/client.rb +144 -13
  67. data/lib/flow_chat/whatsapp/configuration.rb +1 -1
  68. data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +144 -106
  69. data/lib/flow_chat/whatsapp/id_generator.rb +124 -0
  70. data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +147 -0
  71. data/lib/flow_chat/whatsapp/renderer.rb +145 -11
  72. data/lib/flow_chat.rb +11 -1
  73. data/lib/tasks/release.rake +165 -0
  74. metadata +88 -21
  75. data/docs/flows.md +0 -320
  76. data/docs/images/simulator.png +0 -0
  77. data/docs/media.md +0 -153
  78. data/docs/sessions.md +0 -433
  79. data/docs/ussd-setup.md +0 -322
  80. data/docs/whatsapp-setup.md +0 -162
  81. data/examples/whatsapp_message_job.rb +0 -113
  82. data/lib/flow_chat/base_processor.rb +0 -145
  83. data/lib/flow_chat/session/rails_session_store.rb +0 -68
  84. data/lib/flow_chat/ussd/app.rb +0 -6
  85. data/lib/flow_chat/ussd/gateway/nsano.rb +0 -98
  86. data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
  87. data/lib/flow_chat/ussd/processor.rb +0 -39
  88. data/lib/flow_chat/whatsapp/app.rb +0 -29
  89. data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
  90. data/lib/flow_chat/whatsapp/processor.rb +0 -32
  91. data/lib/flow_chat/whatsapp/send_job_support.rb +0 -79
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example: Custom Session ID Configuration with Proc
4
+ #
5
+ # This example demonstrates how to use a custom proc for session ID generation
6
+ # in FlowChat applications. The proc allows complete customization of how
7
+ # session IDs are generated based on context data.
8
+
9
+ require "flow_chat"
10
+
11
+ class CustomSessionIdController < ApplicationController
12
+ def ussd_endpoint
13
+ processor = FlowChat::Processor.new(self) do |config|
14
+ config.use_gateway FlowChat::Ussd::Gateway::Nalo
15
+ config.use_session_store FlowChat::Session::CacheSessionStore
16
+
17
+ # Example 1: Custom session ID using block/proc
18
+ config.use_session_config do |context|
19
+ # Create a custom session ID based on your business logic
20
+ user_phone = context["request.msisdn"]
21
+ flow_name = context["flow.name"]
22
+ gateway = context["request.gateway"]
23
+ timestamp = Time.current.strftime("%Y%m%d")
24
+
25
+ # Custom format: flow_gateway_date_hashedphone
26
+ "#{flow_name}_#{gateway}_#{timestamp}_#{hash_phone(user_phone)}"
27
+ end
28
+ end
29
+
30
+ processor.run(SurveyFlow, :main_menu)
31
+ end
32
+
33
+ def whatsapp_endpoint
34
+ processor = FlowChat::Processor.new(self) do |config|
35
+ config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
36
+ config.use_session_store FlowChat::Session::CacheSessionStore
37
+
38
+ # Example 2: Multi-tenant session IDs
39
+ config.use_session_config do |context|
40
+ tenant_id = extract_tenant_from_request(context)
41
+ user_id = context["request.user_id"] || context["request.msisdn"]
42
+ flow_name = context["flow.name"]
43
+
44
+ "tenant_#{tenant_id}_flow_#{flow_name}_user_#{hash_identifier(user_id)}"
45
+ end
46
+ end
47
+
48
+ processor.run(CustomerSupportFlow, :handle_inquiry)
49
+ end
50
+
51
+ def http_endpoint
52
+ processor = FlowChat::Processor.new(self) do |config|
53
+ config.use_gateway FlowChat::Http::Gateway::Simple
54
+ config.use_session_store FlowChat::Session::CacheSessionStore
55
+
56
+ # Example 3: API session with custom expiration tracking
57
+ config.use_session_config do |context|
58
+ api_key = context.controller.request.headers["X-API-Key"]
59
+ request_id = context["request.id"]
60
+
61
+ # Include API key hash for session isolation per API client
62
+ "api_#{hash_identifier(api_key)}_req_#{request_id}"
63
+ end
64
+ end
65
+
66
+ processor.run(ApiFlow, :handle_request)
67
+ end
68
+
69
+ private
70
+
71
+ def hash_phone(phone)
72
+ require "digest"
73
+ Digest::SHA256.hexdigest(phone)[0, 8]
74
+ end
75
+
76
+ def hash_identifier(identifier)
77
+ require "digest"
78
+ Digest::SHA256.hexdigest(identifier.to_s)[0, 8]
79
+ end
80
+
81
+ def extract_tenant_from_request(context)
82
+ # Extract tenant from subdomain or header
83
+ request = context.controller&.request
84
+ return "default" unless request
85
+
86
+ host = request.host
87
+ subdomain = host.split(".").first
88
+ subdomain if subdomain != "www"
89
+ end
90
+ end
91
+
92
+ # Example flows for demonstration
93
+ class SurveyFlow < FlowChat::Flow
94
+ def main_menu
95
+ app.screen(:menu) { |p| p.ask "Welcome! Choose an option:", choices: ["Survey", "Exit"] }
96
+ end
97
+ end
98
+
99
+ class CustomerSupportFlow < FlowChat::Flow
100
+ def handle_inquiry
101
+ app.screen(:inquiry) { |p| p.ask "How can we help you today?" }
102
+ end
103
+ end
104
+
105
+ class ApiFlow < FlowChat::Flow
106
+ def handle_request
107
+ app.screen(:request) { |p| p.ask "API request received. Provide data:" }
108
+ end
109
+ end
110
+
111
+ # Note: The custom session ID proc approach provides:
112
+ # - Complete control over session ID format
113
+ # - Access to full context (request data, flow info, etc.)
114
+ # - Ability to implement complex business logic
115
+ # - Support for multi-tenancy, API authentication, etc.
116
+ #
117
+ # The proc should return a string that will be used as the session ID.
118
+ # Make sure the returned ID is unique for your use case to avoid
119
+ # session collisions.
@@ -0,0 +1,154 @@
1
+ # Example HTTP Controller for FlowChat HTTP Gateway
2
+ #
3
+ # This controller demonstrates how to use FlowChat with HTTP requests
4
+ # for simple JSON-based conversational interfaces.
5
+ #
6
+ # Usage:
7
+ # POST /http/webhook
8
+ # Content-Type: application/json
9
+ #
10
+ # {
11
+ # "session_id": "unique_session_123",
12
+ # "user_id": "user_456",
13
+ # "input": "Hello"
14
+ # }
15
+ #
16
+ # Response:
17
+ # {
18
+ # "type": "prompt",
19
+ # "session_id": "unique_session_123",
20
+ # "user_id": "user_456",
21
+ # "timestamp": "2024-01-01T12:00:00Z",
22
+ # "message": "Hello! What's your name?",
23
+ # "choices": [
24
+ # {"key": "1", "text": "Continue"}
25
+ # ]
26
+ # }
27
+
28
+ class HttpController < ApplicationController
29
+ skip_forgery_protection
30
+
31
+ def webhook
32
+ processor = FlowChat::Processor.new(self) do |config|
33
+ config.use_gateway FlowChat::Http::Gateway::Simple
34
+ config.use_session_store FlowChat::Session::CacheSessionStore
35
+
36
+ # Configure session management
37
+ config.use_session_config(
38
+ boundaries: [:flow, :platform],
39
+ hash_identifiers: true,
40
+ identifier: :msisdn # Use phone number for durable sessions
41
+ )
42
+ end
43
+
44
+ processor.run WelcomeFlow, :main_page
45
+ end
46
+ end
47
+
48
+ # Example flow for HTTP gateway
49
+ class WelcomeFlow < FlowChat::Flow
50
+ def main_page
51
+ name = app.screen(:name) do |prompt|
52
+ prompt.ask "Hello! What's your name?",
53
+ validate: ->(input) { "Name is required" if input.blank? },
54
+ transform: ->(input) { input.strip.titleize }
55
+ end
56
+
57
+ age = app.screen(:age) do |prompt|
58
+ prompt.ask "Nice to meet you, #{name}! How old are you?",
59
+ validate: ->(input) {
60
+ return "Please enter a number" unless input.match?(/^\d+$/)
61
+ return "Age must be between 1 and 120" unless (1..120).cover?(input.to_i)
62
+ nil
63
+ },
64
+ transform: ->(input) { input.to_i }
65
+ end
66
+
67
+ preferences = app.screen(:preferences) do |prompt|
68
+ prompt.select "What are you interested in?", {
69
+ "tech" => "Technology",
70
+ "sports" => "Sports",
71
+ "music" => "Music",
72
+ "travel" => "Travel"
73
+ }
74
+ end
75
+
76
+ # Summary
77
+ app.say "Great! Here's what I learned about you:"
78
+ app.say "Name: #{name}"
79
+ app.say "Age: #{age}"
80
+ app.say "Interest: #{preferences.capitalize}"
81
+
82
+ # Ask if they want to continue
83
+ continue = app.screen(:continue) do |prompt|
84
+ prompt.yes? "Would you like to explore more features?"
85
+ end
86
+
87
+ if continue
88
+ features_demo
89
+ else
90
+ app.say "Thanks for trying FlowChat HTTP Gateway! 👋"
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def features_demo
97
+ choice = app.screen(:feature_choice) do |prompt|
98
+ prompt.select "What would you like to try?", {
99
+ "media" => "Media Support",
100
+ "validation" => "Input Validation",
101
+ "session" => "Session Management"
102
+ }
103
+ end
104
+
105
+ case choice
106
+ when "media"
107
+ media_demo
108
+ when "validation"
109
+ validation_demo
110
+ when "session"
111
+ session_demo
112
+ end
113
+ end
114
+
115
+ def media_demo
116
+ app.say "FlowChat supports rich media in HTTP responses!",
117
+ media: {
118
+ url: "https://via.placeholder.com/300x200.png?text=FlowChat+HTTP",
119
+ type: :image,
120
+ caption: "FlowChat HTTP Gateway Demo"
121
+ }
122
+
123
+ app.say "Media is returned in the JSON response for your frontend to display."
124
+ end
125
+
126
+ def validation_demo
127
+ email = app.screen(:email) do |prompt|
128
+ prompt.ask "Enter your email address:",
129
+ validate: ->(input) {
130
+ return "Email is required" if input.blank?
131
+ return "Invalid email format" unless input.match?(/\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i)
132
+ nil
133
+ },
134
+ transform: ->(input) { input.downcase.strip }
135
+ end
136
+
137
+ app.say "Perfect! Your email #{email} has been validated and normalized."
138
+ end
139
+
140
+ def session_demo
141
+ # Store some data in session
142
+ app.session.set("demo_timestamp", Time.current.to_s)
143
+ app.session.set("demo_counter", (app.session.get("demo_counter") || 0) + 1)
144
+
145
+ counter = app.session.get("demo_counter")
146
+ timestamp = app.session.get("demo_timestamp")
147
+
148
+ app.say "Session Demo:"
149
+ app.say "• This is visit ##{counter} in this session"
150
+ app.say "• Session started at: #{timestamp}"
151
+ app.say "• Session data persists across HTTP requests"
152
+ app.say "• Session ID: #{app.session.context["session.id"]}"
153
+ end
154
+ end
@@ -0,0 +1,118 @@
1
+ # Example configuration for using FlowChat with Intercom
2
+
3
+ # 1. Rails Credentials Configuration (recommended)
4
+ # Run: rails credentials:edit
5
+ # Add to config/credentials.yml.enc:
6
+
7
+ # intercom:
8
+ # access_token: "your_intercom_access_token_here"
9
+ # client_secret: "your_intercom_client_secret_here" # For webhook signature validation
10
+ # admin_id: "your_admin_id_here" # Required - admin ID for sending messages
11
+ # skip_signature_validation: false # Set to true for development/testing
12
+
13
+ # 2. Environment Variables Configuration (alternative)
14
+ # Set these environment variables:
15
+
16
+ # INTERCOM_ACCESS_TOKEN=your_intercom_access_token_here
17
+ # INTERCOM_CLIENT_SECRET=your_intercom_client_secret_here
18
+ # INTERCOM_ADMIN_ID=your_admin_id_here
19
+ # INTERCOM_SKIP_SIGNATURE_VALIDATION=false
20
+
21
+ # 3. Named Configuration Example (for multi-tenant apps)
22
+ class MultiTenantIntercomSetup
23
+ def self.setup_configurations
24
+ # Main company configuration
25
+ FlowChat::Intercom::Configuration.new("main") do |config|
26
+ config.access_token = Rails.application.credentials.dig(:intercom, :main, :access_token)
27
+ config.client_secret = Rails.application.credentials.dig(:intercom, :main, :client_secret)
28
+ config.admin_id = Rails.application.credentials.dig(:intercom, :main, :admin_id)
29
+ end
30
+
31
+ # Enterprise customer configuration
32
+ FlowChat::Intercom::Configuration.new("enterprise") do |config|
33
+ config.access_token = Rails.application.credentials.dig(:intercom, :enterprise, :access_token)
34
+ config.client_secret = Rails.application.credentials.dig(:intercom, :enterprise, :client_secret)
35
+ config.admin_id = Rails.application.credentials.dig(:intercom, :enterprise, :admin_id)
36
+ end
37
+ end
38
+ end
39
+
40
+ # 4. Routes Configuration
41
+ # Add to config/routes.rb:
42
+
43
+ # Rails.application.routes.draw do
44
+ # # Intercom webhook endpoint - supports both HEAD (for URL validation) and POST (for events)
45
+ # match '/intercom/webhook', to: 'intercom#webhook', via: [:head, :post]
46
+ #
47
+ # # Multiple tenant support (if needed)
48
+ # match '/intercom/:tenant/webhook', to: 'intercom#webhook', via: [:head, :post]
49
+ # end
50
+
51
+ # 5. Controller with Named Configuration
52
+ class TenantAwareIntercomController < ApplicationController
53
+ skip_forgery_protection
54
+
55
+ def webhook
56
+ # Get tenant-specific configuration
57
+ tenant = params[:tenant] || "main"
58
+ FlowChat::Intercom::Configuration.get(tenant)
59
+
60
+ processor = FlowChat::Processor.new(self) do |config|
61
+ config.use_gateway FlowChat::Intercom::Gateway::IntercomApi, config
62
+ config.use_session_store FlowChat::Session::CacheSessionStore
63
+ config.use_session_config(
64
+ boundaries: [:flow, :url], # Separate sessions per tenant
65
+ identifier: :conversation_id
66
+ )
67
+ end
68
+
69
+ processor.run CustomerSupportFlow, :handle_conversation
70
+ end
71
+ end
72
+
73
+ # 6. Intercom Webhook Setup Instructions
74
+ # To set up webhooks in Intercom:
75
+ #
76
+ # 1. Go to your Intercom Developer Hub
77
+ # 2. Navigate to Configure → Webhooks
78
+ # 3. Add your webhook endpoint:
79
+ # - Endpoint URL: https://yourdomain.com/intercom/webhook (must use HTTPS)
80
+ # - Intercom will send a HEAD request to validate your URL
81
+ # - Your app automatically responds with 200 OK
82
+ #
83
+ # 4. Subscribe to webhook topics:
84
+ # - conversation.user.created (new conversations)
85
+ # - conversation.user.replied (user replies)
86
+ #
87
+ # 5. Configure signature validation:
88
+ # - Find your app's Client Secret in Basic Information
89
+ # - Add this as client_secret in your Rails credentials
90
+ # - Webhooks are validated using X-Hub-Signature header with SHA1 HMAC
91
+ #
92
+ # 6. Test the webhook:
93
+ # - Send a test message in Intercom
94
+ # - Verify your endpoint receives the webhook notification
95
+ # - Check logs for any signature validation issues
96
+
97
+ # 7. Finding Your Admin ID (Required)
98
+ # To find your admin ID for message sending, use this Rails console command:
99
+ #
100
+ # rails console
101
+ # ```
102
+ # config = FlowChat::Intercom::Configuration.from_credentials
103
+ # client = FlowChat::Intercom::Client.new(config)
104
+ # result = client.list_admins
105
+ # result["admins"].each { |admin| puts "#{admin["name"]} (#{admin["email"]}) - ID: #{admin["id"]}" }
106
+ # ```
107
+ #
108
+ # This will display all admins with their IDs. Copy the ID of the admin you want to use for sending messages.
109
+ #
110
+ # Alternative methods:
111
+ # 1. Go to your Intercom Developer Hub (https://developers.intercom.com/)
112
+ # 2. Navigate to your app → Configure → Basic Information
113
+ # 3. Find the Admin ID in the app details
114
+ #
115
+ # The admin_id is required for sending messages from FlowChat to Intercom conversations.
116
+ # The API uses this to identify which admin is sending the message.
117
+ #
118
+ # Note: The old bot_user_id configuration is no longer needed.
@@ -0,0 +1,194 @@
1
+ # Example controller showing how to use the Intercom gateway
2
+ # This controller handles webhooks from Intercom for conversation events
3
+
4
+ class IntercomController < ApplicationController
5
+ skip_forgery_protection
6
+
7
+ # POST /intercom/webhook
8
+ # Handle Intercom webhook notifications for conversation events
9
+ def webhook
10
+ processor = FlowChat::Processor.new(self) do |config|
11
+ # Use Intercom gateway with automatic configuration loading
12
+ config.use_gateway FlowChat::Intercom::Gateway::IntercomApi
13
+
14
+ # Use cache-based session storage for longer-lived sessions
15
+ config.use_session_store FlowChat::Session::CacheSessionStore
16
+
17
+ # Configure session boundaries - use conversation ID for session isolation
18
+ config.use_session_config(
19
+ boundaries: [:conversation], # Each conversation gets its own session
20
+ identifier: :conversation_id # Use conversation ID as session key
21
+ )
22
+ end
23
+
24
+ # Run the customer support flow
25
+ processor.run CustomerSupportFlow, :handle_conversation
26
+ end
27
+ end
28
+
29
+ # Example flow for handling customer conversations via Intercom
30
+ class CustomerSupportFlow < FlowChat::Flow
31
+ def handle_conversation
32
+ # Greet the user
33
+ greeting = if first_message?
34
+ "Hello! I'm here to help you. How can I assist you today?"
35
+ else
36
+ "Thanks for your message! Let me help you with that."
37
+ end
38
+
39
+ # Get the user's inquiry
40
+ inquiry = app.screen(:inquiry) do |prompt|
41
+ prompt.ask greeting
42
+ end
43
+
44
+ # Categorize the inquiry and provide appropriate response
45
+ category = categorize_inquiry(inquiry)
46
+
47
+ case category
48
+ when :technical_support
49
+ handle_technical_support
50
+ when :billing
51
+ handle_billing_inquiry
52
+ when :general
53
+ handle_general_inquiry
54
+ when :escalate
55
+ escalate_to_human("Complex issue requiring human attention")
56
+ else
57
+ app.say "I understand you need help. Let me connect you with one of our team members who can assist you better."
58
+ escalate_to_human("Unrecognized inquiry type")
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def first_message?
65
+ # Check if this is the first message in the conversation
66
+ app.session.get("message_count").nil?
67
+ end
68
+
69
+ def categorize_inquiry(inquiry)
70
+ # Simple keyword-based categorization
71
+ # In a real app, you might use AI/ML for better categorization
72
+ text = inquiry.downcase
73
+
74
+ if text.include?("bug") || text.include?("error") || text.include?("not working")
75
+ :technical_support
76
+ elsif text.include?("bill") || text.include?("payment") || text.include?("refund")
77
+ :billing
78
+ elsif text.include?("urgent") || text.include?("important") || text.include?("asap")
79
+ :escalate
80
+ else
81
+ :general
82
+ end
83
+ end
84
+
85
+ def handle_technical_support
86
+ solution = app.screen(:tech_solution) do |prompt|
87
+ prompt.select "I can help you with technical issues! What type of problem are you experiencing?", {
88
+ "login" => "Can't log in",
89
+ "performance" => "App is slow",
90
+ "features" => "Feature not working",
91
+ "other" => "Something else"
92
+ }
93
+ end
94
+
95
+ case solution
96
+ when "login"
97
+ provide_login_help
98
+ when "performance"
99
+ provide_performance_tips
100
+ when "features"
101
+ provide_feature_help
102
+ when "other"
103
+ escalate_to_human("Technical issue requiring human review")
104
+ end
105
+ end
106
+
107
+ def handle_billing_inquiry
108
+ app.say "For billing questions, I'm connecting you with our billing team who can access your account securely."
109
+ escalate_to_human("Billing inquiry", team_id: "billing_team_id")
110
+ end
111
+
112
+ def handle_general_inquiry
113
+ satisfaction = app.screen(:satisfaction) do |prompt|
114
+ prompt.select "I hope I was able to help! How would you rate your experience?", {
115
+ "1" => "😞 Poor",
116
+ "2" => "😐 Okay",
117
+ "3" => "😊 Good",
118
+ "4" => "😍 Excellent"
119
+ }
120
+ end
121
+
122
+ if satisfaction.to_i >= 3
123
+ app.say "Thank you for the positive feedback! Feel free to reach out if you need anything else."
124
+ else
125
+ app.say "I'm sorry the experience wasn't better. Let me connect you with a team member who can help."
126
+ escalate_to_human("User reported poor experience")
127
+ end
128
+ end
129
+
130
+ def provide_login_help
131
+ app.say <<~MESSAGE
132
+ Here are some steps to try for login issues:
133
+
134
+ 1. Make sure you're using the correct email address
135
+ 2. Try resetting your password using the "Forgot Password" link
136
+ 3. Clear your browser cache and cookies
137
+ 4. Try logging in from an incognito/private window
138
+
139
+ If none of these work, I'll connect you with our technical support team.
140
+ MESSAGE
141
+
142
+ still_stuck = app.screen(:login_resolved) do |prompt|
143
+ prompt.yes? "Did these steps help resolve your login issue?"
144
+ end
145
+
146
+ unless still_stuck
147
+ escalate_to_human("Login issue not resolved by self-service steps")
148
+ end
149
+ end
150
+
151
+ def provide_performance_tips
152
+ app.say <<~MESSAGE
153
+ Here are some tips to improve app performance:
154
+
155
+ 1. Close other browser tabs or apps
156
+ 2. Check your internet connection
157
+ 3. Try refreshing the page
158
+ 4. Update your browser to the latest version
159
+
160
+ If the problem persists, our technical team can investigate further.
161
+ MESSAGE
162
+ end
163
+
164
+ def provide_feature_help
165
+ feature = app.screen(:which_feature) do |prompt|
166
+ prompt.ask "Which feature are you having trouble with? Please describe what you're trying to do."
167
+ end
168
+
169
+ app.say "Thanks for the details about #{feature}. Let me connect you with our product team who can provide specific guidance."
170
+ escalate_to_human("Feature support request: #{feature}")
171
+ end
172
+
173
+ def escalate_to_human(reason, team_id: nil)
174
+ conversation_id = app.context["request.conversation_id"]
175
+
176
+ # Business logic: Use Intercom manager for conversation control
177
+ if app.gateway == :intercom_api
178
+ # Access the gateway's client and manager for escalation
179
+ # This would be implemented by exposing these through the gateway
180
+ app.say "I'm connecting you with one of our team members who can provide more personalized assistance. They'll be with you shortly!"
181
+
182
+ # Business logic would handle:
183
+ # - Remove AI_HANDLING tag
184
+ # - Add ESCALATED tag
185
+ # - Assign to appropriate team
186
+ # - Set priority based on reason
187
+ # - Add internal note for context
188
+
189
+ FlowChat.logger.info { "CustomerSupportFlow: Escalating conversation #{conversation_id} - Reason: #{reason}" }
190
+ else
191
+ app.say "Let me transfer you to a human agent who can help you further."
192
+ end
193
+ end
194
+ end
@@ -9,7 +9,7 @@ class MultiTenantWhatsappController < ApplicationController
9
9
  tenant = determine_tenant(request)
10
10
  whatsapp_config = get_whatsapp_config_for_tenant(tenant)
11
11
 
12
- processor = FlowChat::Whatsapp::Processor.new(self, enable_simulator: !Rails.env.production?) do |config|
12
+ processor = FlowChat::Processor.new(self, enable_simulator: !Rails.env.production?) do |config|
13
13
  config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, whatsapp_config
14
14
  config.use_session_store FlowChat::Session::CacheSessionStore
15
15
  end
@@ -96,7 +96,7 @@ class DatabaseWhatsappController < ApplicationController
96
96
  config.app_secret = business_account.whatsapp_app_secret
97
97
  end
98
98
 
99
- processor = FlowChat::Whatsapp::Processor.new(self) do |config|
99
+ processor = FlowChat::Processor.new(self) do |config|
100
100
  config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, whatsapp_config
101
101
  config.use_session_store FlowChat::Session::CacheSessionStore
102
102
  end
@@ -135,7 +135,7 @@ class EnvironmentWhatsappController < ApplicationController
135
135
  FlowChat::Whatsapp::Configuration.from_credentials
136
136
  end
137
137
 
138
- processor = FlowChat::Whatsapp::Processor.new(self) do |config|
138
+ processor = FlowChat::Processor.new(self) do |config|
139
139
  config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, whatsapp_config
140
140
  config.use_session_store FlowChat::Session::CacheSessionStore
141
141
  end
@@ -193,7 +193,7 @@ class CustomWhatsappController < ApplicationController
193
193
  my_config.app_secret = "your_app_secret"
194
194
  my_config.business_account_id = "your_business_account_id"
195
195
 
196
- processor = FlowChat::Whatsapp::Processor.new(self) do |config|
196
+ processor = FlowChat::Processor.new(self) do |config|
197
197
  config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, my_config
198
198
  config.use_session_store FlowChat::Session::CacheSessionStore
199
199
  end
@@ -31,6 +31,15 @@ class SimulatorController < ApplicationController
31
31
  icon: "💬",
32
32
  color: "#25D366"
33
33
  },
34
+ http_main: {
35
+ name: "Main HTTP API",
36
+ description: "JSON HTTP API endpoint",
37
+ processor_type: "http",
38
+ gateway: "http_simple",
39
+ endpoint: "/http/webhook",
40
+ icon: "🌐",
41
+ color: "#0066cc"
42
+ },
34
43
  whatsapp_tenant_a: {
35
44
  name: "Tenant A WhatsApp",
36
45
  description: "Multi-tenant endpoint for Tenant A",
@@ -40,6 +49,15 @@ class SimulatorController < ApplicationController
40
49
  icon: "🏢",
41
50
  color: "#fd7e14"
42
51
  },
52
+ http_external: {
53
+ name: "External HTTP Test",
54
+ description: "Test with external HTTP server",
55
+ processor_type: "http",
56
+ gateway: "http_simple",
57
+ endpoint: "http://localhost:4567/http/webhook",
58
+ icon: "🔗",
59
+ color: "#17a2b8"
60
+ },
43
61
  whatsapp_legacy: {
44
62
  name: "Legacy WhatsApp",
45
63
  description: "Legacy endpoint for compatibility",
@@ -54,7 +72,7 @@ class SimulatorController < ApplicationController
54
72
 
55
73
  # Default configuration to start with
56
74
  def default_config_key
57
- :whatsapp_main
75
+ :http_main
58
76
  end
59
77
 
60
78
  # Default test phone number
@@ -79,8 +97,10 @@ end
79
97
  # 5. View request/response logs in real-time
80
98
 
81
99
  # This allows you to test:
100
+ # - Different protocol types (USSD, WhatsApp, HTTP)
82
101
  # - Different controller implementations on the same server
83
102
  # - Different API versions (v1, v2, etc.)
84
103
  # - Multi-tenant endpoints with different configurations
85
104
  # - Legacy endpoints alongside new ones
105
+ # - External HTTP servers (run examples/http_simulator_test.rb)
86
106
  # - Different flow implementations for different endpoints