flow_chat 0.8.2 → 0.10.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 (147) 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/pages.yml +43 -0
  5. data/.github/workflows/release.yml +56 -0
  6. data/.standard.yml +4 -0
  7. data/CHANGELOG.md +48 -0
  8. data/CLAUDE.md +327 -0
  9. data/CONTRIBUTING.md +134 -0
  10. data/Gemfile +1 -0
  11. data/README.md +189 -133
  12. data/Rakefile +17 -2
  13. data/SECURITY.md +42 -349
  14. data/docs/architecture.md +83 -0
  15. data/docs/async-background-processing.md +64 -0
  16. data/docs/configuration.md +110 -287
  17. data/docs/factory-pattern.md +58 -0
  18. data/docs/gateway-context-variables.md +168 -0
  19. data/docs/gateway-development.md +159 -0
  20. data/docs/getting-started.md +90 -0
  21. data/docs/instrumentation.md +95 -175
  22. data/docs/platforms/instagram.md +278 -0
  23. data/docs/platforms/messenger.md +205 -0
  24. data/docs/platforms/telegram.md +109 -0
  25. data/docs/platforms/ussd.md +78 -0
  26. data/docs/platforms/whatsapp.md +147 -0
  27. data/docs/superpowers/plans/2026-07-09-inbound-media-support.md +732 -0
  28. data/docs/superpowers/plans/2026-07-09-inbound-media-support.md.tasks.json +58 -0
  29. data/docs/superpowers/plans/2026-08-10-messenger-instagram.md +4064 -0
  30. data/docs/superpowers/plans/2026-08-10-messenger-instagram.md.tasks.json +226 -0
  31. data/docs/superpowers/plans/2026-08-16-unified-choice-resolution.md +972 -0
  32. data/docs/superpowers/plans/2026-08-16-unified-choice-resolution.md.tasks.json +88 -0
  33. data/docs/superpowers/specs/2026-07-09-inbound-media-support-design.md +195 -0
  34. data/docs/superpowers/specs/2026-08-10-messenger-instagram-design.md +391 -0
  35. data/docs/testing.md +33 -426
  36. data/examples/custom_session_id_example.rb +119 -0
  37. data/examples/http_controller.rb +22 -20
  38. data/examples/intercom_configuration_example.rb +113 -0
  39. data/examples/intercom_controller.rb +182 -0
  40. data/examples/multi_tenant_whatsapp_controller.rb +63 -168
  41. data/examples/simulator_controller.rb +0 -1
  42. data/examples/ussd_controller.rb +88 -160
  43. data/examples/whatsapp_controller.rb +18 -17
  44. data/examples/whatsapp_media_examples.rb +27 -79
  45. data/flow_chat.gemspec +4 -0
  46. data/lib/flow_chat/app.rb +211 -0
  47. data/lib/flow_chat/async_job.rb +176 -0
  48. data/lib/flow_chat/choice_titles.rb +95 -0
  49. data/lib/flow_chat/config.rb +126 -23
  50. data/lib/flow_chat/delivery_error.rb +9 -0
  51. data/lib/flow_chat/{base_executor.rb → executor.rb} +6 -11
  52. data/lib/flow_chat/factory.rb +94 -0
  53. data/lib/flow_chat/gateway_async_support.rb +106 -0
  54. data/lib/flow_chat/generic_async_job.rb +30 -0
  55. data/lib/flow_chat/http/configuration_error.rb +9 -0
  56. data/lib/flow_chat/http/gateway/simple.rb +104 -36
  57. data/lib/flow_chat/http/middleware/choice_mapper.rb +94 -0
  58. data/lib/flow_chat/http/renderer.rb +3 -3
  59. data/lib/flow_chat/input.rb +86 -0
  60. data/lib/flow_chat/instagram/client.rb +32 -0
  61. data/lib/flow_chat/instagram/configuration.rb +147 -0
  62. data/lib/flow_chat/instagram/configuration_error.rb +7 -0
  63. data/lib/flow_chat/instagram/gateway/send_api.rb +63 -0
  64. data/lib/flow_chat/instagram/middleware/choice_mapper.rb +22 -0
  65. data/lib/flow_chat/instagram/renderer.rb +23 -0
  66. data/lib/flow_chat/instrumentation/metrics_collector.rb +6 -1
  67. data/lib/flow_chat/instrumentation/setup.rb +1 -1
  68. data/lib/flow_chat/instrumentation.rb +182 -0
  69. data/lib/flow_chat/intercom/client.rb +161 -0
  70. data/lib/flow_chat/intercom/configuration.rb +102 -0
  71. data/lib/flow_chat/intercom/configuration_error.rb +9 -0
  72. data/lib/flow_chat/intercom/gateway/intercom_api.rb +420 -0
  73. data/lib/flow_chat/intercom/middleware/choice_mapper.rb +101 -0
  74. data/lib/flow_chat/intercom/renderer.rb +123 -0
  75. data/lib/flow_chat/media.rb +121 -0
  76. data/lib/flow_chat/messenger/client.rb +264 -0
  77. data/lib/flow_chat/messenger/configuration.rb +103 -0
  78. data/lib/flow_chat/messenger/configuration_error.rb +9 -0
  79. data/lib/flow_chat/messenger/gateway/send_api.rb +42 -0
  80. data/lib/flow_chat/messenger/middleware/choice_mapper.rb +185 -0
  81. data/lib/flow_chat/messenger/renderer.rb +150 -0
  82. data/lib/flow_chat/meta/challenge.rb +24 -0
  83. data/lib/flow_chat/meta/choice_ladder.rb +37 -0
  84. data/lib/flow_chat/meta/configuration_error.rb +7 -0
  85. data/lib/flow_chat/meta/gateway_identity.rb +38 -0
  86. data/lib/flow_chat/meta/messaging_gateway.rb +468 -0
  87. data/lib/flow_chat/meta/signature.rb +30 -0
  88. data/lib/flow_chat/meta/signature_validation.rb +66 -0
  89. data/lib/flow_chat/meta/webhook_verification.rb +43 -0
  90. data/lib/flow_chat/named_configuration.rb +65 -0
  91. data/lib/flow_chat/phone_number_util.rb +37 -35
  92. data/lib/flow_chat/processor.rb +188 -0
  93. data/lib/flow_chat/prompt.rb +13 -16
  94. data/lib/flow_chat/renderers/markdown_support.rb +167 -0
  95. data/lib/flow_chat/security.rb +76 -0
  96. data/lib/flow_chat/session/middleware.rb +36 -11
  97. data/lib/flow_chat/simulator/controller.rb +31 -15
  98. data/lib/flow_chat/simulator/views/simulator.html.erb +184 -20
  99. data/lib/flow_chat/telegram/client.rb +283 -0
  100. data/lib/flow_chat/telegram/configuration.rb +78 -0
  101. data/lib/flow_chat/telegram/configuration_error.rb +9 -0
  102. data/lib/flow_chat/telegram/gateway/bot_api.rb +318 -0
  103. data/lib/flow_chat/telegram/middleware/choice_mapper.rb +96 -0
  104. data/lib/flow_chat/telegram/renderer.rb +133 -0
  105. data/lib/flow_chat/telegram.rb +7 -0
  106. data/lib/flow_chat/text_truncator.rb +75 -0
  107. data/lib/flow_chat/ussd/gateway/nalo.rb +24 -4
  108. data/lib/flow_chat/ussd/middleware/choice_mapper.rb +10 -0
  109. data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
  110. data/lib/flow_chat/ussd/renderer.rb +1 -1
  111. data/lib/flow_chat/version.rb +1 -1
  112. data/lib/flow_chat/whatsapp/client.rb +158 -20
  113. data/lib/flow_chat/whatsapp/configuration.rb +13 -52
  114. data/lib/flow_chat/whatsapp/configuration_error.rb +9 -0
  115. data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +335 -248
  116. data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +234 -0
  117. data/lib/flow_chat/whatsapp/renderer.rb +259 -64
  118. data/lib/flow_chat.rb +1 -1
  119. data/lib/tasks/release.rake +165 -0
  120. data/site/.nojekyll +0 -0
  121. data/site/.og-card.html +89 -0
  122. data/site/favicon.svg +6 -0
  123. data/site/index.html +209 -0
  124. data/site/og.png +0 -0
  125. metadata +132 -25
  126. data/docs/flows.md +0 -320
  127. data/docs/http-gateway-protocol.md +0 -432
  128. data/docs/images/simulator.png +0 -0
  129. data/docs/media.md +0 -153
  130. data/docs/sessions.md +0 -433
  131. data/docs/ussd-setup.md +0 -322
  132. data/docs/whatsapp-setup.md +0 -162
  133. data/examples/whatsapp_message_job.rb +0 -113
  134. data/lib/flow_chat/base_app.rb +0 -86
  135. data/lib/flow_chat/base_processor.rb +0 -146
  136. data/lib/flow_chat/http/app.rb +0 -6
  137. data/lib/flow_chat/http/middleware/executor.rb +0 -24
  138. data/lib/flow_chat/http/processor.rb +0 -33
  139. data/lib/flow_chat/session/rails_session_store.rb +0 -68
  140. data/lib/flow_chat/ussd/app.rb +0 -6
  141. data/lib/flow_chat/ussd/gateway/nsano.rb +0 -96
  142. data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
  143. data/lib/flow_chat/ussd/processor.rb +0 -39
  144. data/lib/flow_chat/whatsapp/app.rb +0 -29
  145. data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
  146. data/lib/flow_chat/whatsapp/processor.rb +0 -32
  147. data/lib/flow_chat/whatsapp/send_job_support.rb +0 -79
@@ -6,10 +6,10 @@
6
6
  # Usage:
7
7
  # POST /http/webhook
8
8
  # Content-Type: application/json
9
- #
9
+ #
10
10
  # {
11
11
  # "session_id": "unique_session_123",
12
- # "user_id": "user_456",
12
+ # "user_id": "user_456",
13
13
  # "input": "Hello"
14
14
  # }
15
15
  #
@@ -29,24 +29,26 @@ class HttpController < ApplicationController
29
29
  skip_forgery_protection
30
30
 
31
31
  def webhook
32
- processor = FlowChat::Http::Processor.new(self) do |config|
32
+ processor = FlowChat::Processor.new(self) do |config|
33
33
  config.use_gateway FlowChat::Http::Gateway::Simple
34
34
  config.use_session_store FlowChat::Session::CacheSessionStore
35
-
36
- # Configure session management
35
+
36
+ # The HTTP gateway sets request.user_id from the request's user_id, so key
37
+ # sessions on it. (:msisdn would be nil here, collapsing every caller into
38
+ # one session.)
37
39
  config.use_session_config(
38
40
  boundaries: [:flow, :platform],
39
41
  hash_identifiers: true,
40
- identifier: :msisdn # Use phone number for durable sessions
42
+ identifier: :user_id
41
43
  )
42
44
  end
43
45
 
44
- processor.run WelcomeFlow, :main_page
46
+ processor.run HttpWelcomeFlow, :main_page
45
47
  end
46
48
  end
47
49
 
48
50
  # Example flow for HTTP gateway
49
- class WelcomeFlow < FlowChat::Flow
51
+ class HttpWelcomeFlow < FlowChat::Flow
50
52
  def main_page
51
53
  name = app.screen(:name) do |prompt|
52
54
  prompt.ask "Hello! What's your name?",
@@ -56,9 +58,9 @@ class WelcomeFlow < FlowChat::Flow
56
58
 
57
59
  age = app.screen(:age) do |prompt|
58
60
  prompt.ask "Nice to meet you, #{name}! How old are you?",
59
- validate: ->(input) {
61
+ validate: ->(input) {
60
62
  return "Please enter a number" unless input.match?(/^\d+$/)
61
- return "Age must be between 1 and 120" unless (1..120).include?(input.to_i)
63
+ return "Age must be between 1 and 120" unless (1..120).cover?(input.to_i)
62
64
  nil
63
65
  },
64
66
  transform: ->(input) { input.to_i }
@@ -67,7 +69,7 @@ class WelcomeFlow < FlowChat::Flow
67
69
  preferences = app.screen(:preferences) do |prompt|
68
70
  prompt.select "What are you interested in?", {
69
71
  "tech" => "Technology",
70
- "sports" => "Sports",
72
+ "sports" => "Sports",
71
73
  "music" => "Music",
72
74
  "travel" => "Travel"
73
75
  }
@@ -78,7 +80,7 @@ class WelcomeFlow < FlowChat::Flow
78
80
  app.say "Name: #{name}"
79
81
  app.say "Age: #{age}"
80
82
  app.say "Interest: #{preferences.capitalize}"
81
-
83
+
82
84
  # Ask if they want to continue
83
85
  continue = app.screen(:continue) do |prompt|
84
86
  prompt.yes? "Would you like to explore more features?"
@@ -87,7 +89,7 @@ class WelcomeFlow < FlowChat::Flow
87
89
  if continue
88
90
  features_demo
89
91
  else
90
- app.say "Thanks for trying FlowChat HTTP Gateway! 👋"
92
+ app.say "Thanks for trying the FlowChat HTTP gateway!"
91
93
  end
92
94
  end
93
95
 
@@ -115,7 +117,7 @@ class WelcomeFlow < FlowChat::Flow
115
117
  def media_demo
116
118
  app.say "FlowChat supports rich media in HTTP responses!",
117
119
  media: {
118
- url: "https://via.placeholder.com/300x200.png?text=FlowChat+HTTP",
120
+ url: "https://example.com/images/flowchat-http-demo.png",
119
121
  type: :image,
120
122
  caption: "FlowChat HTTP Gateway Demo"
121
123
  }
@@ -128,7 +130,7 @@ class WelcomeFlow < FlowChat::Flow
128
130
  prompt.ask "Enter your email address:",
129
131
  validate: ->(input) {
130
132
  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)
133
+ return "Invalid email format" unless input.match?(/\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i)
132
134
  nil
133
135
  },
134
136
  transform: ->(input) { input.downcase.strip }
@@ -146,9 +148,9 @@ class WelcomeFlow < FlowChat::Flow
146
148
  timestamp = app.session.get("demo_timestamp")
147
149
 
148
150
  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']}"
151
+ app.say "This is visit ##{counter} in this session"
152
+ app.say "Session started at: #{timestamp}"
153
+ app.say "Session data persists across HTTP requests"
154
+ app.say "Session ID: #{app.context["session.id"]}"
153
155
  end
154
- end
156
+ end
@@ -0,0 +1,113 @@
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
+ # Passing a name registers the configuration under that name, so you can
25
+ # fetch it later with Configuration.get(:main). Set attributes with tap;
26
+ # Configuration.new does not yield a block.
27
+ FlowChat::Intercom::Configuration.new("main").tap do |config|
28
+ config.access_token = Rails.application.credentials.dig(:intercom, :main, :access_token)
29
+ config.client_secret = Rails.application.credentials.dig(:intercom, :main, :client_secret)
30
+ config.admin_id = Rails.application.credentials.dig(:intercom, :main, :admin_id)
31
+ end
32
+
33
+ # Enterprise customer configuration
34
+ FlowChat::Intercom::Configuration.new("enterprise").tap do |config|
35
+ config.access_token = Rails.application.credentials.dig(:intercom, :enterprise, :access_token)
36
+ config.client_secret = Rails.application.credentials.dig(:intercom, :enterprise, :client_secret)
37
+ config.admin_id = Rails.application.credentials.dig(:intercom, :enterprise, :admin_id)
38
+ end
39
+ end
40
+ end
41
+
42
+ # 4. Routes Configuration
43
+ # Add to config/routes.rb:
44
+
45
+ # Rails.application.routes.draw do
46
+ # # Intercom webhook endpoint - supports both HEAD (for URL validation) and POST (for events)
47
+ # match '/intercom/webhook', to: 'intercom#webhook', via: [:head, :post]
48
+ #
49
+ # # Multiple tenant support (if needed)
50
+ # match '/intercom/:tenant/webhook', to: 'intercom#webhook', via: [:head, :post]
51
+ # end
52
+
53
+ # 5. Controller with Named Configuration
54
+ class TenantAwareIntercomController < ApplicationController
55
+ skip_forgery_protection
56
+
57
+ def webhook
58
+ # Fetch the named configuration registered in setup_configurations above.
59
+ tenant = params[:tenant] || "main"
60
+ tenant_config = FlowChat::Intercom::Configuration.get(tenant)
61
+
62
+ processor = FlowChat::Processor.new(self) do |config|
63
+ config.use_gateway FlowChat::Intercom::Gateway::IntercomApi, tenant_config
64
+ config.use_session_store FlowChat::Session::CacheSessionStore
65
+ # request.id is the conversation id (the default :request_id identifier),
66
+ # so each conversation gets its own session. The :url boundary separates
67
+ # tenants that share the flow.
68
+ config.use_session_config(boundaries: [:flow, :url])
69
+ end
70
+
71
+ processor.run CustomerSupportFlow, :handle_conversation
72
+ end
73
+ end
74
+
75
+ # 6. Intercom Webhook Setup Instructions
76
+ # To set up webhooks in Intercom:
77
+ #
78
+ # 1. Go to your Intercom Developer Hub
79
+ # 2. Navigate to Configure → Webhooks
80
+ # 3. Add your webhook endpoint:
81
+ # - Endpoint URL: https://yourdomain.com/intercom/webhook (must use HTTPS)
82
+ # - Intercom will send a HEAD request to validate your URL
83
+ # - Your app automatically responds with 200 OK
84
+ #
85
+ # 4. Subscribe to webhook topics:
86
+ # - conversation.user.created (new conversations)
87
+ # - conversation.user.replied (user replies)
88
+ #
89
+ # 5. Configure signature validation:
90
+ # - Find your app's Client Secret in Basic Information
91
+ # - Add this as client_secret in your Rails credentials
92
+ # - Webhooks are validated using X-Hub-Signature header with SHA1 HMAC
93
+ #
94
+ # 6. Test the webhook:
95
+ # - Send a test message in Intercom
96
+ # - Verify your endpoint receives the webhook notification
97
+ # - Check logs for any signature validation issues
98
+
99
+ # 7. Finding Your Admin ID (Required)
100
+ # The admin_id identifies which admin sends messages to a conversation.
101
+ # FlowChat's own client only sends messages, so list admins with the official
102
+ # intercom gem in a Rails console:
103
+ #
104
+ # rails console
105
+ # ```
106
+ # intercom = Intercom::Client.new(token: Rails.application.credentials.dig(:intercom, :access_token))
107
+ # intercom.admins.all.each { |admin| puts "#{admin.name} (#{admin.email}) - ID: #{admin.id}" }
108
+ # ```
109
+ #
110
+ # Copy the ID of the admin you want to send from. You can also find it in the
111
+ # Intercom Developer Hub under your app's Configure -> Basic Information.
112
+ #
113
+ # Note: The old bot_user_id configuration is no longer needed.
@@ -0,0 +1,182 @@
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
+ # The Intercom gateway sets request.id to the conversation id, and the
18
+ # default identifier (:request_id) uses it, so each conversation already
19
+ # gets its own session. Scope by flow as well.
20
+ config.use_session_config(boundaries: [:flow])
21
+ end
22
+
23
+ # Run the customer support flow
24
+ processor.run CustomerSupportFlow, :handle_conversation
25
+ end
26
+ end
27
+
28
+ # Example flow for handling customer conversations via Intercom
29
+ class CustomerSupportFlow < FlowChat::Flow
30
+ def handle_conversation
31
+ # Ask the opening question. The prompt is only shown on the first unanswered
32
+ # turn; once the user replies, screen(:inquiry) returns their stored answer.
33
+ inquiry = app.screen(:inquiry) do |prompt|
34
+ prompt.ask "Hello! I'm here to help you. How can I assist you today?"
35
+ end
36
+
37
+ # Categorize the inquiry and provide appropriate response
38
+ category = categorize_inquiry(inquiry)
39
+
40
+ case category
41
+ when :technical_support
42
+ handle_technical_support
43
+ when :billing
44
+ handle_billing_inquiry
45
+ when :general
46
+ handle_general_inquiry
47
+ when :escalate
48
+ escalate_to_human("Complex issue requiring human attention")
49
+ else
50
+ app.say "I understand you need help. Let me connect you with one of our team members who can assist you better."
51
+ escalate_to_human("Unrecognized inquiry type")
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def categorize_inquiry(inquiry)
58
+ # Simple keyword-based categorization
59
+ # In a real app, you might use AI/ML for better categorization
60
+ text = inquiry.downcase
61
+
62
+ if text.include?("bug") || text.include?("error") || text.include?("not working")
63
+ :technical_support
64
+ elsif text.include?("bill") || text.include?("payment") || text.include?("refund")
65
+ :billing
66
+ elsif text.include?("urgent") || text.include?("important") || text.include?("asap")
67
+ :escalate
68
+ else
69
+ :general
70
+ end
71
+ end
72
+
73
+ def handle_technical_support
74
+ solution = app.screen(:tech_solution) do |prompt|
75
+ prompt.select "I can help you with technical issues! What type of problem are you experiencing?", {
76
+ "login" => "Can't log in",
77
+ "performance" => "App is slow",
78
+ "features" => "Feature not working",
79
+ "other" => "Something else"
80
+ }
81
+ end
82
+
83
+ case solution
84
+ when "login"
85
+ provide_login_help
86
+ when "performance"
87
+ provide_performance_tips
88
+ when "features"
89
+ provide_feature_help
90
+ when "other"
91
+ escalate_to_human("Technical issue requiring human review")
92
+ end
93
+ end
94
+
95
+ def handle_billing_inquiry
96
+ app.say "For billing questions, I'm connecting you with our billing team who can access your account securely."
97
+ escalate_to_human("Billing inquiry", team_id: "billing_team_id")
98
+ end
99
+
100
+ def handle_general_inquiry
101
+ satisfaction = app.screen(:satisfaction) do |prompt|
102
+ prompt.select "I hope I was able to help! How would you rate your experience?", {
103
+ "1" => "😞 Poor",
104
+ "2" => "😐 Okay",
105
+ "3" => "😊 Good",
106
+ "4" => "😍 Excellent"
107
+ }
108
+ end
109
+
110
+ if satisfaction.to_i >= 3
111
+ app.say "Thank you for the positive feedback! Feel free to reach out if you need anything else."
112
+ else
113
+ app.say "I'm sorry the experience wasn't better. Let me connect you with a team member who can help."
114
+ escalate_to_human("User reported poor experience")
115
+ end
116
+ end
117
+
118
+ def provide_login_help
119
+ app.say <<~MESSAGE
120
+ Here are some steps to try for login issues:
121
+
122
+ 1. Make sure you're using the correct email address
123
+ 2. Try resetting your password using the "Forgot Password" link
124
+ 3. Clear your browser cache and cookies
125
+ 4. Try logging in from an incognito/private window
126
+
127
+ If none of these work, I'll connect you with our technical support team.
128
+ MESSAGE
129
+
130
+ still_stuck = app.screen(:login_resolved) do |prompt|
131
+ prompt.yes? "Did these steps help resolve your login issue?"
132
+ end
133
+
134
+ unless still_stuck
135
+ escalate_to_human("Login issue not resolved by self-service steps")
136
+ end
137
+ end
138
+
139
+ def provide_performance_tips
140
+ app.say <<~MESSAGE
141
+ Here are some tips to improve app performance:
142
+
143
+ 1. Close other browser tabs or apps
144
+ 2. Check your internet connection
145
+ 3. Try refreshing the page
146
+ 4. Update your browser to the latest version
147
+
148
+ If the problem persists, our technical team can investigate further.
149
+ MESSAGE
150
+ end
151
+
152
+ def provide_feature_help
153
+ feature = app.screen(:which_feature) do |prompt|
154
+ prompt.ask "Which feature are you having trouble with? Please describe what you're trying to do."
155
+ end
156
+
157
+ app.say "Thanks for the details about #{feature}. Let me connect you with our product team who can provide specific guidance."
158
+ escalate_to_human("Feature support request: #{feature}")
159
+ end
160
+
161
+ def escalate_to_human(reason, team_id: nil)
162
+ conversation_id = app.context["request.id"]
163
+
164
+ # Business logic: Use Intercom manager for conversation control
165
+ if app.gateway == :intercom_api
166
+ # Access the gateway's client and manager for escalation
167
+ # This would be implemented by exposing these through the gateway
168
+ app.say "I'm connecting you with one of our team members who can provide more personalized assistance. They'll be with you shortly!"
169
+
170
+ # Business logic would handle:
171
+ # - Remove AI_HANDLING tag
172
+ # - Add ESCALATED tag
173
+ # - Assign to appropriate team
174
+ # - Set priority based on reason
175
+ # - Add internal note for context
176
+
177
+ FlowChat.logger.info { "CustomerSupportFlow: Escalating conversation #{conversation_id} - Reason: #{reason}" }
178
+ else
179
+ app.say "Let me transfer you to a human agent who can help you further."
180
+ end
181
+ end
182
+ end