flow_chat 0.4.1 → 0.4.2
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/examples/initializer.rb +1 -1
- data/examples/media_prompts_examples.rb +1 -2
- data/examples/multi_tenant_whatsapp_controller.rb +56 -56
- data/examples/ussd_controller.rb +17 -11
- data/examples/whatsapp_controller.rb +10 -10
- data/examples/whatsapp_media_examples.rb +78 -80
- data/examples/whatsapp_message_job.rb +3 -3
- data/lib/flow_chat/base_processor.rb +1 -1
- data/lib/flow_chat/config.rb +4 -3
- data/lib/flow_chat/session/cache_session_store.rb +5 -5
- data/lib/flow_chat/simulator/views/simulator.html.erb +287 -12
- data/lib/flow_chat/ussd/gateway/nsano.rb +1 -1
- data/lib/flow_chat/ussd/processor.rb +1 -1
- data/lib/flow_chat/ussd/prompt.rb +13 -13
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/app.rb +1 -1
- data/lib/flow_chat/whatsapp/client.rb +41 -45
- data/lib/flow_chat/whatsapp/configuration.rb +10 -10
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +8 -10
- data/lib/flow_chat/whatsapp/middleware/executor.rb +1 -1
- data/lib/flow_chat/whatsapp/processor.rb +1 -1
- data/lib/flow_chat/whatsapp/prompt.rb +27 -31
- data/lib/flow_chat/whatsapp/send_job_support.rb +7 -7
- data/lib/flow_chat/whatsapp/template_manager.rb +7 -7
- metadata +1 -1
@@ -63,19 +63,19 @@ class MediaSupportFlow < FlowChat::Flow
|
|
63
63
|
private
|
64
64
|
|
65
65
|
def handle_received_media
|
66
|
-
media_type = app.media[
|
67
|
-
media_id = app.media[
|
68
|
-
|
66
|
+
media_type = app.media["type"]
|
67
|
+
media_id = app.media["id"]
|
68
|
+
|
69
69
|
Rails.logger.info "Received #{media_type} from #{app.phone_number}: #{media_id}"
|
70
|
-
|
70
|
+
|
71
71
|
case media_type
|
72
|
-
when
|
72
|
+
when "image"
|
73
73
|
app.say "Thanks for the image! I can see it's a #{media_type} file. Let me process it for you."
|
74
|
-
when
|
74
|
+
when "document"
|
75
75
|
app.say "I've received your document. I'll review it and get back to you shortly."
|
76
|
-
when
|
76
|
+
when "audio"
|
77
77
|
app.say "Got your voice message! I'll listen to it and respond appropriately."
|
78
|
-
when
|
78
|
+
when "video"
|
79
79
|
app.say "Thanks for the video! I'll analyze it and provide feedback."
|
80
80
|
end
|
81
81
|
end
|
@@ -83,32 +83,32 @@ class MediaSupportFlow < FlowChat::Flow
|
|
83
83
|
def send_product_catalog
|
84
84
|
# Send multiple product images from URLs
|
85
85
|
client = get_whatsapp_client
|
86
|
-
|
86
|
+
|
87
87
|
app.say "Here's our latest product catalog:"
|
88
|
-
|
88
|
+
|
89
89
|
# Product images stored in cloud storage (CDN, S3, etc.)
|
90
90
|
product_images = [
|
91
91
|
"https://cdn.example.com/products/product1.jpg",
|
92
|
-
"https://cdn.example.com/products/product2.jpg",
|
92
|
+
"https://cdn.example.com/products/product2.jpg",
|
93
93
|
"https://cdn.example.com/products/product3.jpg"
|
94
94
|
]
|
95
|
-
|
95
|
+
|
96
96
|
product_images.each_with_index do |image_url, index|
|
97
97
|
client.send_image(app.phone_number, image_url, "Product #{index + 1}")
|
98
98
|
sleep(0.5) # Small delay to avoid rate limiting
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
app.say "Which product interests you the most?"
|
102
102
|
end
|
103
103
|
|
104
104
|
def send_report
|
105
105
|
# Send a PDF report from cloud storage
|
106
106
|
report_url = generate_report_url # Your method to generate/get report URL
|
107
|
-
|
107
|
+
|
108
108
|
if report_url
|
109
109
|
client = get_whatsapp_client
|
110
110
|
client.send_document(app.phone_number, report_url, "Your monthly report is ready!")
|
111
|
-
|
111
|
+
|
112
112
|
app.say "📊 Report sent! Please check the document above."
|
113
113
|
else
|
114
114
|
app.say "Sorry, I couldn't generate the report right now. Please try again later."
|
@@ -118,10 +118,10 @@ class MediaSupportFlow < FlowChat::Flow
|
|
118
118
|
def send_voice_message
|
119
119
|
# Send a pre-recorded voice message from cloud storage
|
120
120
|
audio_url = "https://cdn.example.com/audio/support_greeting.mp3"
|
121
|
-
|
121
|
+
|
122
122
|
client = get_whatsapp_client
|
123
123
|
client.send_audio(app.phone_number, audio_url)
|
124
|
-
|
124
|
+
|
125
125
|
app.say "🎵 Please listen to the voice message above. You can also send me a voice message with your question!"
|
126
126
|
end
|
127
127
|
|
@@ -129,15 +129,15 @@ class MediaSupportFlow < FlowChat::Flow
|
|
129
129
|
feedback = app.screen(:feedback_text) do |prompt|
|
130
130
|
prompt.ask "Please share your feedback. You can also send images or documents if needed:"
|
131
131
|
end
|
132
|
-
|
132
|
+
|
133
133
|
# Save feedback to database
|
134
134
|
save_feedback(feedback, app.phone_number)
|
135
|
-
|
135
|
+
|
136
136
|
# Send a thank you sticker from cloud storage
|
137
137
|
sticker_url = "https://cdn.example.com/stickers/thanks.webp"
|
138
138
|
client = get_whatsapp_client
|
139
139
|
client.send_sticker(app.phone_number, sticker_url)
|
140
|
-
|
140
|
+
|
141
141
|
app.say "Thank you for your feedback! We really appreciate it. 🙏"
|
142
142
|
end
|
143
143
|
|
@@ -149,7 +149,7 @@ class MediaSupportFlow < FlowChat::Flow
|
|
149
149
|
def generate_report_url
|
150
150
|
# Your report generation logic here
|
151
151
|
# This could return a signed URL from S3, Google Cloud Storage, etc.
|
152
|
-
"https://storage.example.com/reports/monthly_report_#{Time.current.strftime(
|
152
|
+
"https://storage.example.com/reports/monthly_report_#{Time.current.strftime("%Y%m")}.pdf"
|
153
153
|
end
|
154
154
|
|
155
155
|
def save_feedback(feedback, phone_number)
|
@@ -187,8 +187,8 @@ class WhatsAppMediaService
|
|
187
187
|
def send_order_confirmation(phone_number, order_id, invoice_url)
|
188
188
|
# Send invoice document from cloud storage
|
189
189
|
@client.send_document(
|
190
|
-
phone_number,
|
191
|
-
invoice_url,
|
190
|
+
phone_number,
|
191
|
+
invoice_url,
|
192
192
|
"Order ##{order_id} confirmed! Here's your invoice.",
|
193
193
|
"Invoice_#{order_id}.pdf"
|
194
194
|
)
|
@@ -198,9 +198,9 @@ class WhatsAppMediaService
|
|
198
198
|
phone_number,
|
199
199
|
"Your order has been confirmed! 🛍️",
|
200
200
|
[
|
201
|
-
{
|
202
|
-
{
|
203
|
-
{
|
201
|
+
{id: "track_order", title: "📦 Track Order"},
|
202
|
+
{id: "modify_order", title: "✏️ Modify Order"},
|
203
|
+
{id: "support", title: "💬 Contact Support"}
|
204
204
|
]
|
205
205
|
)
|
206
206
|
end
|
@@ -220,54 +220,52 @@ class WhatsAppMediaService
|
|
220
220
|
phone_number,
|
221
221
|
"Don't miss out on this amazing deal!",
|
222
222
|
[
|
223
|
-
{
|
224
|
-
{
|
225
|
-
{
|
223
|
+
{id: "buy_now", title: "🛒 Buy Now"},
|
224
|
+
{id: "more_info", title: "ℹ️ More Info"},
|
225
|
+
{id: "remind_later", title: "⏰ Remind Later"}
|
226
226
|
]
|
227
227
|
)
|
228
228
|
end
|
229
229
|
|
230
230
|
# Handle media uploads with processing
|
231
231
|
def process_uploaded_media(media_id, media_type, user_phone)
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
process_video(cloud_url, user_phone)
|
251
|
-
end
|
252
|
-
|
253
|
-
Rails.logger.info "Successfully processed #{media_type} from #{user_phone}"
|
232
|
+
# Download the media from WhatsApp
|
233
|
+
media_url = @client.get_media_url(media_id)
|
234
|
+
media_content = @client.download_media(media_id) if media_url
|
235
|
+
|
236
|
+
if media_content
|
237
|
+
# Upload to your cloud storage (S3, Google Cloud, etc.)
|
238
|
+
cloud_url = upload_to_cloud_storage(media_content, media_type, media_id)
|
239
|
+
|
240
|
+
# Process based on media type
|
241
|
+
case media_type
|
242
|
+
when "image"
|
243
|
+
process_image(cloud_url, user_phone)
|
244
|
+
when "document"
|
245
|
+
process_document(cloud_url, user_phone)
|
246
|
+
when "audio"
|
247
|
+
process_audio(cloud_url, user_phone)
|
248
|
+
when "video"
|
249
|
+
process_video(cloud_url, user_phone)
|
254
250
|
end
|
255
|
-
|
256
|
-
Rails.logger.
|
257
|
-
@client.send_text(user_phone, "Sorry, I couldn't process your file. Please try again.")
|
251
|
+
|
252
|
+
Rails.logger.info "Successfully processed #{media_type} from #{user_phone}"
|
258
253
|
end
|
254
|
+
rescue => e
|
255
|
+
Rails.logger.error "Error processing media: #{e.message}"
|
256
|
+
@client.send_text(user_phone, "Sorry, I couldn't process your file. Please try again.")
|
259
257
|
end
|
260
258
|
|
261
259
|
# Send personalized content based on user data
|
262
260
|
def send_personalized_content(phone_number, user_id)
|
263
261
|
# Get user's preferred content from your system
|
264
262
|
user_content = fetch_user_content(user_id)
|
265
|
-
|
263
|
+
|
266
264
|
# Send personalized image
|
267
265
|
if user_content[:image_url]
|
268
266
|
@client.send_image(phone_number, user_content[:image_url], user_content[:image_caption])
|
269
267
|
end
|
270
|
-
|
268
|
+
|
271
269
|
# Send personalized document
|
272
270
|
if user_content[:document_url]
|
273
271
|
@client.send_document(phone_number, user_content[:document_url], user_content[:document_description])
|
@@ -278,7 +276,7 @@ class WhatsAppMediaService
|
|
278
276
|
def send_qr_code(phone_number, data)
|
279
277
|
# Generate QR code and get URL (using your QR service)
|
280
278
|
qr_url = generate_qr_code_url(data)
|
281
|
-
|
279
|
+
|
282
280
|
@client.send_image(phone_number, qr_url, "Here's your QR code!")
|
283
281
|
end
|
284
282
|
|
@@ -286,7 +284,7 @@ class WhatsAppMediaService
|
|
286
284
|
def send_analytics_chart(phone_number, chart_type, period)
|
287
285
|
# Generate chart URL from your analytics service
|
288
286
|
chart_url = generate_analytics_chart_url(chart_type, period)
|
289
|
-
|
287
|
+
|
290
288
|
@client.send_image(phone_number, chart_url, "#{chart_type.humanize} for #{period}")
|
291
289
|
end
|
292
290
|
|
@@ -340,11 +338,11 @@ class WhatsAppMediaService
|
|
340
338
|
|
341
339
|
def get_file_extension(media_type)
|
342
340
|
case media_type
|
343
|
-
when
|
344
|
-
when
|
345
|
-
when
|
346
|
-
when
|
347
|
-
else
|
341
|
+
when "image" then "jpg"
|
342
|
+
when "document" then "pdf"
|
343
|
+
when "audio" then "mp3"
|
344
|
+
when "video" then "mp4"
|
345
|
+
else "bin"
|
348
346
|
end
|
349
347
|
end
|
350
348
|
end
|
@@ -356,45 +354,45 @@ end
|
|
356
354
|
class NotificationController < ApplicationController
|
357
355
|
def send_media_notification
|
358
356
|
service = WhatsAppMediaService.new
|
359
|
-
|
357
|
+
|
360
358
|
# Send welcome package to new users
|
361
359
|
service.send_welcome_package(params[:phone_number], params[:user_name])
|
362
|
-
|
363
|
-
render json: {
|
360
|
+
|
361
|
+
render json: {status: "sent"}
|
364
362
|
end
|
365
|
-
|
363
|
+
|
366
364
|
def send_order_confirmation
|
367
365
|
service = WhatsAppMediaService.new
|
368
|
-
|
366
|
+
|
369
367
|
# Get invoice URL from your system (could be from S3, Google Cloud, etc.)
|
370
368
|
invoice_url = get_invoice_url(params[:order_id])
|
371
|
-
|
369
|
+
|
372
370
|
service.send_order_confirmation(
|
373
|
-
params[:phone_number],
|
374
|
-
params[:order_id],
|
371
|
+
params[:phone_number],
|
372
|
+
params[:order_id],
|
375
373
|
invoice_url
|
376
374
|
)
|
377
|
-
|
378
|
-
render json: {
|
375
|
+
|
376
|
+
render json: {status: "sent"}
|
379
377
|
end
|
380
378
|
|
381
379
|
def send_promo
|
382
380
|
service = WhatsAppMediaService.new
|
383
|
-
|
381
|
+
|
384
382
|
# Promotional content from CDN
|
385
383
|
promo_image = "https://cdn.example.com/promos/#{params[:promo_id]}/banner.jpg"
|
386
384
|
promo_video = "https://cdn.example.com/promos/#{params[:promo_id]}/video.mp4"
|
387
|
-
|
385
|
+
|
388
386
|
service.send_promotion(params[:phone_number], promo_image, promo_video)
|
389
|
-
|
390
|
-
render json: {
|
387
|
+
|
388
|
+
render json: {status: "sent"}
|
391
389
|
end
|
392
390
|
|
393
391
|
def send_qr_code
|
394
392
|
service = WhatsAppMediaService.new
|
395
393
|
service.send_qr_code(params[:phone_number], params[:qr_data])
|
396
|
-
|
397
|
-
render json: {
|
394
|
+
|
395
|
+
render json: {status: "sent"}
|
398
396
|
end
|
399
397
|
|
400
398
|
private
|
@@ -403,4 +401,4 @@ class NotificationController < ApplicationController
|
|
403
401
|
# Your logic to get invoice URL from cloud storage
|
404
402
|
"https://storage.example.com/invoices/#{order_id}.pdf"
|
405
403
|
end
|
406
|
-
end
|
404
|
+
end
|
@@ -54,7 +54,7 @@ class UrgentWhatsappSendJob < ApplicationJob
|
|
54
54
|
error: error.message,
|
55
55
|
user: send_data[:msisdn]
|
56
56
|
)
|
57
|
-
|
57
|
+
|
58
58
|
# Still send user notification
|
59
59
|
super
|
60
60
|
end
|
@@ -96,7 +96,7 @@ class MultiTenantWhatsappSendJob < ApplicationJob
|
|
96
96
|
end
|
97
97
|
|
98
98
|
# Usage in Rails configuration
|
99
|
-
#
|
99
|
+
#
|
100
100
|
# Add to config/application.rb:
|
101
101
|
# config.active_job.queue_adapter = :sidekiq
|
102
102
|
#
|
@@ -108,4 +108,4 @@ end
|
|
108
108
|
# 1. Controller receives WhatsApp webhook
|
109
109
|
# 2. Flow is processed synchronously (maintains controller context)
|
110
110
|
# 3. Response is queued for async delivery via background job
|
111
|
-
# 4. Job only handles sending the response, not processing flows
|
111
|
+
# 4. Job only handles sending the response, not processing flows
|
data/lib/flow_chat/config.rb
CHANGED
@@ -32,18 +32,19 @@ module FlowChat
|
|
32
32
|
end
|
33
33
|
|
34
34
|
class WhatsappConfig
|
35
|
-
attr_accessor :
|
35
|
+
attr_accessor :background_job_class
|
36
|
+
attr_reader :message_handling_mode
|
36
37
|
|
37
38
|
def initialize
|
38
39
|
@message_handling_mode = :inline
|
39
|
-
@background_job_class =
|
40
|
+
@background_job_class = "WhatsappMessageJob"
|
40
41
|
end
|
41
42
|
|
42
43
|
# Validate message handling mode
|
43
44
|
def message_handling_mode=(mode)
|
44
45
|
valid_modes = [:inline, :background, :simulator]
|
45
46
|
unless valid_modes.include?(mode.to_sym)
|
46
|
-
raise ArgumentError, "Invalid message handling mode: #{mode}. Valid modes: #{valid_modes.join(
|
47
|
+
raise ArgumentError, "Invalid message handling mode: #{mode}. Valid modes: #{valid_modes.join(", ")}"
|
47
48
|
end
|
48
49
|
@message_handling_mode = mode.to_sym
|
49
50
|
end
|
@@ -4,7 +4,7 @@ module FlowChat
|
|
4
4
|
def initialize(context, cache = nil)
|
5
5
|
@context = context
|
6
6
|
@cache = cache || FlowChat::Config.cache
|
7
|
-
|
7
|
+
|
8
8
|
raise ArgumentError, "Cache is required. Set FlowChat::Config.cache or pass a cache instance." unless @cache
|
9
9
|
end
|
10
10
|
|
@@ -22,7 +22,7 @@ module FlowChat
|
|
22
22
|
|
23
23
|
data = @cache.read(session_key) || {}
|
24
24
|
data[key.to_s] = value
|
25
|
-
|
25
|
+
|
26
26
|
@cache.write(session_key, data, expires_in: session_ttl)
|
27
27
|
value
|
28
28
|
end
|
@@ -55,7 +55,7 @@ module FlowChat
|
|
55
55
|
def session_key
|
56
56
|
gateway = @context["request.gateway"]
|
57
57
|
msisdn = @context["request.msisdn"]
|
58
|
-
|
58
|
+
|
59
59
|
case gateway
|
60
60
|
when :whatsapp_cloud_api
|
61
61
|
"flow_chat:session:whatsapp:#{msisdn}"
|
@@ -69,7 +69,7 @@ module FlowChat
|
|
69
69
|
|
70
70
|
def session_ttl
|
71
71
|
gateway = @context["request.gateway"]
|
72
|
-
|
72
|
+
|
73
73
|
case gateway
|
74
74
|
when :whatsapp_cloud_api
|
75
75
|
7.days # WhatsApp conversations can be long-lived
|
@@ -81,4 +81,4 @@ module FlowChat
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
end
|
84
|
-
end
|
84
|
+
end
|