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.
- checksums.yaml +4 -4
- data/.cliff.toml +74 -0
- data/.github/workflows/ci.yml +2 -3
- data/.github/workflows/pages.yml +43 -0
- data/.github/workflows/release.yml +56 -0
- data/.standard.yml +4 -0
- data/CHANGELOG.md +48 -0
- data/CLAUDE.md +327 -0
- data/CONTRIBUTING.md +134 -0
- data/Gemfile +1 -0
- data/README.md +189 -133
- data/Rakefile +17 -2
- data/SECURITY.md +42 -349
- data/docs/architecture.md +83 -0
- data/docs/async-background-processing.md +64 -0
- data/docs/configuration.md +110 -287
- data/docs/factory-pattern.md +58 -0
- data/docs/gateway-context-variables.md +168 -0
- data/docs/gateway-development.md +159 -0
- data/docs/getting-started.md +90 -0
- data/docs/instrumentation.md +95 -175
- data/docs/platforms/instagram.md +278 -0
- data/docs/platforms/messenger.md +205 -0
- data/docs/platforms/telegram.md +109 -0
- data/docs/platforms/ussd.md +78 -0
- data/docs/platforms/whatsapp.md +147 -0
- data/docs/superpowers/plans/2026-07-09-inbound-media-support.md +732 -0
- data/docs/superpowers/plans/2026-07-09-inbound-media-support.md.tasks.json +58 -0
- data/docs/superpowers/plans/2026-08-10-messenger-instagram.md +4064 -0
- data/docs/superpowers/plans/2026-08-10-messenger-instagram.md.tasks.json +226 -0
- data/docs/superpowers/plans/2026-08-16-unified-choice-resolution.md +972 -0
- data/docs/superpowers/plans/2026-08-16-unified-choice-resolution.md.tasks.json +88 -0
- data/docs/superpowers/specs/2026-07-09-inbound-media-support-design.md +195 -0
- data/docs/superpowers/specs/2026-08-10-messenger-instagram-design.md +391 -0
- data/docs/testing.md +33 -426
- data/examples/custom_session_id_example.rb +119 -0
- data/examples/http_controller.rb +22 -20
- data/examples/intercom_configuration_example.rb +113 -0
- data/examples/intercom_controller.rb +182 -0
- data/examples/multi_tenant_whatsapp_controller.rb +63 -168
- data/examples/simulator_controller.rb +0 -1
- data/examples/ussd_controller.rb +88 -160
- data/examples/whatsapp_controller.rb +18 -17
- data/examples/whatsapp_media_examples.rb +27 -79
- data/flow_chat.gemspec +4 -0
- data/lib/flow_chat/app.rb +211 -0
- data/lib/flow_chat/async_job.rb +176 -0
- data/lib/flow_chat/choice_titles.rb +95 -0
- data/lib/flow_chat/config.rb +126 -23
- data/lib/flow_chat/delivery_error.rb +9 -0
- data/lib/flow_chat/{base_executor.rb → executor.rb} +6 -11
- 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/configuration_error.rb +9 -0
- data/lib/flow_chat/http/gateway/simple.rb +104 -36
- data/lib/flow_chat/http/middleware/choice_mapper.rb +94 -0
- data/lib/flow_chat/http/renderer.rb +3 -3
- data/lib/flow_chat/input.rb +86 -0
- data/lib/flow_chat/instagram/client.rb +32 -0
- data/lib/flow_chat/instagram/configuration.rb +147 -0
- data/lib/flow_chat/instagram/configuration_error.rb +7 -0
- data/lib/flow_chat/instagram/gateway/send_api.rb +63 -0
- data/lib/flow_chat/instagram/middleware/choice_mapper.rb +22 -0
- data/lib/flow_chat/instagram/renderer.rb +23 -0
- data/lib/flow_chat/instrumentation/metrics_collector.rb +6 -1
- data/lib/flow_chat/instrumentation/setup.rb +1 -1
- data/lib/flow_chat/instrumentation.rb +182 -0
- data/lib/flow_chat/intercom/client.rb +161 -0
- data/lib/flow_chat/intercom/configuration.rb +102 -0
- data/lib/flow_chat/intercom/configuration_error.rb +9 -0
- data/lib/flow_chat/intercom/gateway/intercom_api.rb +420 -0
- data/lib/flow_chat/intercom/middleware/choice_mapper.rb +101 -0
- data/lib/flow_chat/intercom/renderer.rb +123 -0
- data/lib/flow_chat/media.rb +121 -0
- data/lib/flow_chat/messenger/client.rb +264 -0
- data/lib/flow_chat/messenger/configuration.rb +103 -0
- data/lib/flow_chat/messenger/configuration_error.rb +9 -0
- data/lib/flow_chat/messenger/gateway/send_api.rb +42 -0
- data/lib/flow_chat/messenger/middleware/choice_mapper.rb +185 -0
- data/lib/flow_chat/messenger/renderer.rb +150 -0
- data/lib/flow_chat/meta/challenge.rb +24 -0
- data/lib/flow_chat/meta/choice_ladder.rb +37 -0
- data/lib/flow_chat/meta/configuration_error.rb +7 -0
- data/lib/flow_chat/meta/gateway_identity.rb +38 -0
- data/lib/flow_chat/meta/messaging_gateway.rb +468 -0
- data/lib/flow_chat/meta/signature.rb +30 -0
- data/lib/flow_chat/meta/signature_validation.rb +66 -0
- data/lib/flow_chat/meta/webhook_verification.rb +43 -0
- data/lib/flow_chat/named_configuration.rb +65 -0
- data/lib/flow_chat/phone_number_util.rb +37 -35
- data/lib/flow_chat/processor.rb +188 -0
- data/lib/flow_chat/prompt.rb +13 -16
- data/lib/flow_chat/renderers/markdown_support.rb +167 -0
- data/lib/flow_chat/security.rb +76 -0
- data/lib/flow_chat/session/middleware.rb +36 -11
- data/lib/flow_chat/simulator/controller.rb +31 -15
- data/lib/flow_chat/simulator/views/simulator.html.erb +184 -20
- data/lib/flow_chat/telegram/client.rb +283 -0
- data/lib/flow_chat/telegram/configuration.rb +78 -0
- data/lib/flow_chat/telegram/configuration_error.rb +9 -0
- data/lib/flow_chat/telegram/gateway/bot_api.rb +318 -0
- data/lib/flow_chat/telegram/middleware/choice_mapper.rb +96 -0
- data/lib/flow_chat/telegram/renderer.rb +133 -0
- data/lib/flow_chat/telegram.rb +7 -0
- data/lib/flow_chat/text_truncator.rb +75 -0
- data/lib/flow_chat/ussd/gateway/nalo.rb +24 -4
- data/lib/flow_chat/ussd/middleware/choice_mapper.rb +10 -0
- 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 +158 -20
- data/lib/flow_chat/whatsapp/configuration.rb +13 -52
- data/lib/flow_chat/whatsapp/configuration_error.rb +9 -0
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +335 -248
- data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +234 -0
- data/lib/flow_chat/whatsapp/renderer.rb +259 -64
- data/lib/flow_chat.rb +1 -1
- data/lib/tasks/release.rake +165 -0
- data/site/.nojekyll +0 -0
- data/site/.og-card.html +89 -0
- data/site/favicon.svg +6 -0
- data/site/index.html +209 -0
- data/site/og.png +0 -0
- metadata +132 -25
- data/docs/flows.md +0 -320
- data/docs/http-gateway-protocol.md +0 -432
- 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_app.rb +0 -86
- data/lib/flow_chat/base_processor.rb +0 -146
- data/lib/flow_chat/http/app.rb +0 -6
- data/lib/flow_chat/http/middleware/executor.rb +0 -24
- data/lib/flow_chat/http/processor.rb +0 -33
- 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 -96
- 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
data/examples/http_controller.rb
CHANGED
|
@@ -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::
|
|
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
|
-
#
|
|
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: :
|
|
42
|
+
identifier: :user_id
|
|
41
43
|
)
|
|
42
44
|
end
|
|
43
45
|
|
|
44
|
-
processor.run
|
|
46
|
+
processor.run HttpWelcomeFlow, :main_page
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
# Example flow for HTTP gateway
|
|
49
|
-
class
|
|
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).
|
|
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
|
|
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://
|
|
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
|
|
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 "
|
|
150
|
-
app.say "
|
|
151
|
-
app.say "
|
|
152
|
-
app.say "
|
|
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
|