flow_chat 0.8.0 → 0.8.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/docs/configuration.md +2 -2
  3. data/docs/http-gateway-protocol.md +432 -0
  4. data/docs/sessions.md +7 -7
  5. data/docs/ussd-setup.md +1 -1
  6. data/examples/http_controller.rb +154 -0
  7. data/examples/simulator_controller.rb +21 -1
  8. data/examples/ussd_controller.rb +1 -1
  9. data/lib/flow_chat/base_app.rb +86 -0
  10. data/lib/flow_chat/base_executor.rb +57 -0
  11. data/lib/flow_chat/base_processor.rb +7 -6
  12. data/lib/flow_chat/config.rb +17 -2
  13. data/lib/flow_chat/http/app.rb +6 -0
  14. data/lib/flow_chat/http/gateway/simple.rb +77 -0
  15. data/lib/flow_chat/http/middleware/executor.rb +24 -0
  16. data/lib/flow_chat/http/processor.rb +33 -0
  17. data/lib/flow_chat/http/renderer.rb +41 -0
  18. data/lib/flow_chat/instrumentation/setup.rb +0 -2
  19. data/lib/flow_chat/instrumentation.rb +2 -0
  20. data/lib/flow_chat/interrupt.rb +6 -0
  21. data/lib/flow_chat/phone_number_util.rb +47 -0
  22. data/lib/flow_chat/session/cache_session_store.rb +1 -17
  23. data/lib/flow_chat/session/middleware.rb +19 -18
  24. data/lib/flow_chat/simulator/controller.rb +17 -5
  25. data/lib/flow_chat/simulator/views/simulator.html.erb +220 -8
  26. data/lib/flow_chat/ussd/app.rb +1 -53
  27. data/lib/flow_chat/ussd/gateway/nalo.rb +3 -7
  28. data/lib/flow_chat/ussd/gateway/nsano.rb +0 -2
  29. data/lib/flow_chat/ussd/middleware/executor.rb +11 -37
  30. data/lib/flow_chat/version.rb +1 -1
  31. data/lib/flow_chat/whatsapp/app.rb +11 -46
  32. data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +16 -14
  33. data/lib/flow_chat/whatsapp/middleware/executor.rb +11 -39
  34. data/lib/flow_chat.rb +1 -11
  35. metadata +12 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ebeb5cdcbacce73ec580e381554dbd2377f7896d7d06c488970051f6a8bf599
4
- data.tar.gz: bc33f9f54c78264f416e00163e2ba0cf78da2e8e0961c942bb3aab1e86e783e5
3
+ metadata.gz: 419f60cf88904f992a2bcf4e93646e68adaee3fcdac5a96d080708b199a2d8a6
4
+ data.tar.gz: 24ffafb2b588fdaee9775422e8057c073ce133d693dd6e78f19ef32978d40f88
5
5
  SHA512:
6
- metadata.gz: 34006bb23ddaf1aefd8ec4876cb70d8f06d2bbb23da48663ff7a40dd630d9b01af58d9980c343c9d933fa637512375b5c7ba3b0b336ba91a314fdd9ae5fb2ba1
7
- data.tar.gz: 78a242f2b175f57ce29326f342c9d4bf4c7c4402c0e2fcd634c21322280dcd0ac273d86eb19750702472218e9d08fd97267ad711b3673f108f89617e5319b9f7
6
+ metadata.gz: b49e73727d931e33d9ebff02c0809f31bd3adb7478335be76272961b04db219d98b10664675e32b45998bbcaa9d2d33de3644b84d3d924dc719d797341cd3fe0
7
+ data.tar.gz: 789fe300b2e3863b88e11a347bf2b205440427b2a70a6cec0d3995bd5a5efc4210dfc66e571412ca79cf642d93ba35890ba6d5270eb1c3b23a727b6beb62f326
@@ -24,7 +24,7 @@ FlowChat.setup_instrumentation!
24
24
  ```ruby
25
25
  # Session boundaries control how session IDs are constructed
26
26
  FlowChat::Config.session.boundaries = [:flow, :platform] # default
27
- FlowChat::Config.session.hash_phone_numbers = true # hash phone numbers for privacy
27
+ FlowChat::Config.session.hash_identifiers = true # hash identifiers for privacy
28
28
  FlowChat::Config.session.identifier = nil # let platforms choose (default)
29
29
 
30
30
  # Available boundary options:
@@ -193,7 +193,7 @@ processor = FlowChat::Ussd::Processor.new(self) do |config|
193
193
  # Configure session boundaries
194
194
  config.use_session_config(
195
195
  boundaries: [:flow, :platform], # which boundaries to enforce
196
- hash_phone_numbers: true, # hash phone numbers for privacy
196
+ hash_identifiers: true, # hash identifiers for privacy
197
197
  identifier: :msisdn # use MSISDN for durable sessions (optional)
198
198
  )
199
199
 
@@ -0,0 +1,432 @@
1
+ # FlowChat HTTP Gateway Protocol Specification
2
+
3
+ **Version:** 1.0
4
+ **Date:** 2025-06-27
5
+ **Status:** Stable
6
+
7
+ ## Overview
8
+
9
+ The FlowChat HTTP Gateway Protocol defines a simple JSON-based request/response format for building conversational flows over HTTP. It enables any HTTP endpoint to participate in FlowChat's conversation framework with session management, media support, and interactive elements.
10
+
11
+ ## Protocol Design
12
+
13
+ The protocol is designed to be:
14
+ - **Simple**: Easy to implement with any web framework
15
+ - **Stateless**: Each request contains all necessary context
16
+ - **Flexible**: Supports various response types and media
17
+ - **Extensible**: Can be enhanced without breaking compatibility
18
+
19
+ ## Request Format
20
+
21
+ ### HTTP Method
22
+ All requests use **POST** method.
23
+
24
+ ### Headers
25
+ ```
26
+ Content-Type: application/json
27
+ Accept: application/json
28
+ ```
29
+
30
+ ### Request Body
31
+ ```json
32
+ {
33
+ "session_id": "string",
34
+ "user_id": "string",
35
+ "input": "string",
36
+ "simulator_mode": boolean
37
+ }
38
+ ```
39
+
40
+ #### Request Fields
41
+
42
+ | Field | Type | Required | Description |
43
+ |-------|------|----------|-------------|
44
+ | `session_id` | string | Yes | Unique identifier for the conversation session |
45
+ | `user_id` | string | Yes | Unique identifier for the user (phone number, email, etc.) |
46
+ | `input` | string | Yes | User's input message (empty string for initial request) |
47
+ | `simulator_mode` | boolean | No | Indicates if request is from FlowChat simulator |
48
+
49
+ #### Example Request
50
+ ```json
51
+ {
52
+ "session_id": "sess_abc123",
53
+ "user_id": "+256700123456",
54
+ "input": "1",
55
+ "simulator_mode": true
56
+ }
57
+ ```
58
+
59
+ ## Response Format
60
+
61
+ ### HTTP Status Codes
62
+ - **200 OK**: Successful response with flow continuation
63
+ - **400 Bad Request**: Invalid request format
64
+ - **500 Internal Server Error**: Server processing error
65
+
66
+ ### Response Body
67
+ ```json
68
+ {
69
+ "type": "string",
70
+ "message": "string",
71
+ "choices": "object|null",
72
+ "media": "object|null"
73
+ }
74
+ ```
75
+
76
+ #### Response Fields
77
+
78
+ | Field | Type | Required | Description |
79
+ |-------|------|----------|-------------|
80
+ | `type` | string | Yes | Response type: `prompt`, `text`, or `terminal` |
81
+ | `message` | string | Yes | Text message to display to user |
82
+ | `choices` | object | No | Interactive choices for user selection |
83
+ | `media` | object | No | Media content (images, videos, etc.) |
84
+
85
+ ## Response Types
86
+
87
+ ### 1. Prompt Response
88
+ Interactive response expecting user input with optional choices.
89
+
90
+ ```json
91
+ {
92
+ "type": "prompt",
93
+ "message": "Welcome! Please choose an option:",
94
+ "choices": {
95
+ "1": "View Profile",
96
+ "2": "Send Message",
97
+ "3": "Settings"
98
+ }
99
+ }
100
+ ```
101
+
102
+ **Behavior**: Session continues, awaiting user input.
103
+
104
+ ### 2. Text Response
105
+ Simple informational message.
106
+
107
+ ```json
108
+ {
109
+ "type": "text",
110
+ "message": "Processing your request..."
111
+ }
112
+ ```
113
+
114
+ **Behavior**: Session continues, awaiting user input.
115
+
116
+ ### 3. Terminal Response
117
+ Final message that ends the conversation session.
118
+
119
+ ```json
120
+ {
121
+ "type": "terminal",
122
+ "message": "Thank you for using our service. Goodbye!"
123
+ }
124
+ ```
125
+
126
+ **Behavior**: Session ends, no further input expected.
127
+
128
+ ## Choices Format
129
+
130
+ Choices enable interactive menu-driven flows. They can be provided in two formats:
131
+
132
+ ### Simple Format
133
+ ```json
134
+ {
135
+ "choices": {
136
+ "1": "Option One",
137
+ "2": "Option Two",
138
+ "3": "Option Three"
139
+ }
140
+ }
141
+ ```
142
+
143
+ ### Object Format
144
+ ```json
145
+ {
146
+ "choices": {
147
+ "choice1": {
148
+ "key": "1",
149
+ "value": "Option One"
150
+ },
151
+ "choice2": {
152
+ "key": "2",
153
+ "value": "Option Two"
154
+ }
155
+ }
156
+ }
157
+ ```
158
+
159
+ **Key Guidelines:**
160
+ - Keys should be simple (numbers, single letters)
161
+ - Values should be descriptive text
162
+ - Maximum 10 choices recommended for usability
163
+ - Empty choices object `{}` means free-form input expected
164
+
165
+ ## Media Support
166
+
167
+ The protocol supports various media types in responses.
168
+
169
+ ### Image Media
170
+ ```json
171
+ {
172
+ "media": {
173
+ "type": "image",
174
+ "url": "https://example.com/image.jpg",
175
+ "caption": "Optional image caption"
176
+ }
177
+ }
178
+ ```
179
+
180
+ ### Video Media
181
+ ```json
182
+ {
183
+ "media": {
184
+ "type": "video",
185
+ "url": "https://example.com/video.mp4",
186
+ "caption": "Optional video caption"
187
+ }
188
+ }
189
+ ```
190
+
191
+ ### Document Media
192
+ ```json
193
+ {
194
+ "media": {
195
+ "type": "document",
196
+ "url": "https://example.com/document.pdf",
197
+ "filename": "report.pdf",
198
+ "caption": "Download the full report"
199
+ }
200
+ }
201
+ ```
202
+
203
+ ### Audio Media
204
+ ```json
205
+ {
206
+ "media": {
207
+ "type": "audio",
208
+ "url": "https://example.com/audio.mp3",
209
+ "caption": "Voice message"
210
+ }
211
+ }
212
+ ```
213
+
214
+ **Media Guidelines:**
215
+ - URLs must be publicly accessible
216
+ - HTTPS URLs recommended for security
217
+ - Include appropriate file extensions
218
+ - Captions are optional but recommended for accessibility
219
+
220
+ ## Session Management
221
+
222
+ ### Session Lifecycle
223
+ 1. **Initialization**: First request has empty `input` field
224
+ 2. **Continuation**: Subsequent requests include user input
225
+ 3. **Termination**: Response with `type: "terminal"` ends session
226
+
227
+ ### Session State
228
+ The protocol is stateless from HTTP perspective. Session state must be managed by the endpoint implementation using:
229
+ - Database storage
230
+ - In-memory cache
231
+ - External session stores
232
+ - Cookies/tokens (if supported)
233
+
234
+ ### Session ID Format
235
+ - Must be unique across all sessions
236
+ - Recommended: UUID, random string, or timestamped identifier
237
+ - Length: 8-64 characters
238
+ - Characters: alphanumeric, hyphens, underscores
239
+
240
+ ## Error Handling
241
+
242
+ ### Client Errors (4xx)
243
+ ```json
244
+ {
245
+ "type": "terminal",
246
+ "message": "Invalid request format. Please try again later."
247
+ }
248
+ ```
249
+
250
+ ### Server Errors (5xx)
251
+ ```json
252
+ {
253
+ "type": "terminal",
254
+ "message": "Service temporarily unavailable. Please try again later."
255
+ }
256
+ ```
257
+
258
+ ### Validation Errors
259
+ ```json
260
+ {
261
+ "type": "prompt",
262
+ "message": "Invalid selection. Please choose from the available options:",
263
+ "choices": {
264
+ "1": "Option A",
265
+ "2": "Option B"
266
+ }
267
+ }
268
+ ```
269
+
270
+ ## Implementation Examples
271
+
272
+ ### Basic Flow Handler
273
+ ```ruby
274
+ def handle_http_webhook
275
+ data = JSON.parse(request.body.read)
276
+
277
+ session_id = data['session_id']
278
+ user_id = data['user_id']
279
+ input = data['input']
280
+
281
+ response = case input.downcase.strip
282
+ when '', 'start'
283
+ {
284
+ type: 'prompt',
285
+ message: 'Welcome! What would you like to do?',
286
+ choices: {
287
+ '1' => 'View Account',
288
+ '2' => 'Make Payment',
289
+ '3' => 'Get Help'
290
+ }
291
+ }
292
+ when '1'
293
+ {
294
+ type: 'text',
295
+ message: "Account Balance: $150.00\nLast Transaction: -$25.00"
296
+ }
297
+ when '2'
298
+ {
299
+ type: 'prompt',
300
+ message: 'Enter payment amount:',
301
+ choices: {}
302
+ }
303
+ when '3'
304
+ {
305
+ type: 'terminal',
306
+ message: 'For help, call 1-800-HELP or visit our website.'
307
+ }
308
+ else
309
+ {
310
+ type: 'prompt',
311
+ message: 'Please select a valid option:',
312
+ choices: {
313
+ '1' => 'View Account',
314
+ '2' => 'Make Payment',
315
+ '3' => 'Get Help'
316
+ }
317
+ }
318
+ end
319
+
320
+ render json: response
321
+ end
322
+ ```
323
+
324
+ ### With Media Support
325
+ ```ruby
326
+ def handle_media_flow
327
+ # ... input parsing ...
328
+
329
+ case input
330
+ when 'show_chart'
331
+ {
332
+ type: 'prompt',
333
+ message: 'Here is your sales chart:',
334
+ media: {
335
+ type: 'image',
336
+ url: generate_chart_url(user_id),
337
+ caption: 'Monthly sales data'
338
+ },
339
+ choices: {
340
+ '0' => 'Back to menu'
341
+ }
342
+ }
343
+ end
344
+ end
345
+ ```
346
+
347
+ ## Security Considerations
348
+
349
+ ### Input Validation
350
+ - Always validate and sanitize user input
351
+ - Check session_id format and existence
352
+ - Validate user_id against expected format
353
+ - Limit input length to prevent abuse
354
+
355
+ ### Authentication
356
+ - Implement endpoint authentication if needed
357
+ - Validate session ownership
358
+ - Use HTTPS for sensitive data
359
+ - Consider rate limiting
360
+
361
+ ### Data Protection
362
+ - Don't log sensitive user input
363
+ - Encrypt session data if stored
364
+ - Use secure session identifiers
365
+ - Implement proper CORS headers
366
+
367
+ ## Best Practices
368
+
369
+ ### Response Design
370
+ 1. **Keep messages concise** - Users prefer short, clear text
371
+ 2. **Limit choices** - Maximum 5-7 options for better UX
372
+ 3. **Use clear labels** - Make choice text descriptive
373
+ 4. **Provide fallbacks** - Handle invalid input gracefully
374
+ 5. **End flows properly** - Always provide terminal responses
375
+
376
+ ### Session Management
377
+ 1. **Set timeouts** - Expire inactive sessions
378
+ 2. **Clean up data** - Remove old session data
379
+ 3. **Handle duplicates** - Manage duplicate session IDs
380
+ 4. **Log activities** - Track session flow for debugging
381
+
382
+ ### Error Handling
383
+ 1. **Fail gracefully** - Provide helpful error messages
384
+ 2. **Log errors** - Capture errors for debugging
385
+ 3. **Retry logic** - Handle temporary failures
386
+ 4. **Fallback flows** - Provide alternative paths
387
+
388
+ ## Protocol Extensions
389
+
390
+ ### Custom Fields
391
+ Implementations may include additional fields in requests/responses:
392
+
393
+ ```json
394
+ {
395
+ "type": "prompt",
396
+ "message": "Hello!",
397
+ "choices": {...},
398
+ "metadata": {
399
+ "flow_version": "1.2",
400
+ "user_lang": "en"
401
+ }
402
+ }
403
+ ```
404
+
405
+ ### Future Enhancements
406
+ - Rich media support (carousels, buttons)
407
+ - Location sharing
408
+ - File uploads
409
+ - Real-time messaging
410
+ - Multi-turn conversations
411
+
412
+ ## Compliance
413
+
414
+ ### HTTP Standards
415
+ - Follows HTTP/1.1 and HTTP/2 specifications
416
+ - Uses standard status codes and headers
417
+ - Supports CORS for cross-origin requests
418
+
419
+ ### JSON Standards
420
+ - Follows RFC 7159 JSON specification
421
+ - Uses UTF-8 encoding
422
+ - Validates JSON schema on input
423
+
424
+ ## Version History
425
+
426
+ | Version | Date | Changes |
427
+ |---------|------|---------|
428
+ | 1.0 | 2025-06-27 | Initial specification |
429
+
430
+ ---
431
+
432
+ For implementation questions or protocol clarifications, please refer to the FlowChat documentation or contact the development team.
data/docs/sessions.md CHANGED
@@ -23,7 +23,7 @@ Controls how session IDs are generated and sessions are isolated:
23
23
  ```ruby
24
24
  # Global session configuration
25
25
  FlowChat::Config.session.boundaries = [:flow, :gateway, :platform] # isolation boundaries
26
- FlowChat::Config.session.hash_phone_numbers = true # privacy protection
26
+ FlowChat::Config.session.hash_identifiers = true # privacy protection
27
27
  FlowChat::Config.session.identifier = nil # platform chooses default
28
28
  ```
29
29
 
@@ -99,15 +99,15 @@ identifier: :msisdn
99
99
  # Same session resumes across conversations
100
100
  ```
101
101
 
102
- ### Phone Number Hashing
102
+ ### Identifier Hashing
103
103
 
104
104
  When using `:msisdn` identifier, phone numbers are automatically hashed for privacy:
105
105
 
106
106
  ```ruby
107
- FlowChat::Config.session.hash_phone_numbers = true # default
107
+ FlowChat::Config.session.hash_identifiers = true # default
108
108
  # "+256700123456" becomes "a1b2c3d4" (8-character hash)
109
109
 
110
- FlowChat::Config.session.hash_phone_numbers = false
110
+ FlowChat::Config.session.hash_identifiers = false
111
111
  # "+256700123456" used directly (not recommended for production)
112
112
  ```
113
113
 
@@ -135,7 +135,7 @@ processor = FlowChat::Ussd::Processor.new(self) do |config|
135
135
  # Explicit session configuration
136
136
  config.use_session_config(
137
137
  boundaries: [:flow, :platform], # isolate by flow and platform
138
- hash_phone_numbers: true, # hash phone numbers for privacy
138
+ hash_identifiers: true, # hash identifiers for privacy
139
139
  identifier: :msisdn # use phone number for durable sessions
140
140
  )
141
141
  end
@@ -352,8 +352,8 @@ end
352
352
  ### 5. Security Considerations
353
353
 
354
354
  ```ruby
355
- # Always hash phone numbers in production
356
- FlowChat::Config.session.hash_phone_numbers = true
355
+ # Always hash identifiers in production
356
+ FlowChat::Config.session.hash_identifiers = true
357
357
 
358
358
  # Use secure cache backends
359
359
  config.cache_store = :redis_cache_store, {
data/docs/ussd-setup.md CHANGED
@@ -147,7 +147,7 @@ Session identifier options:
147
147
  - **`nil`** - Platform chooses default (`:request_id` for USSD, `:msisdn` for WhatsApp)
148
148
  - **`:msisdn`** - Use phone number (durable sessions)
149
149
  - **`:request_id`** - Use request ID (ephemeral sessions)
150
- - **`hash_phone_numbers`** - Hash phone numbers for privacy (recommended)
150
+ - **`hash_identifiers`** - Hash identifiers for privacy (recommended)
151
151
 
152
152
  ## Middleware
153
153
 
@@ -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::Http::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).include?(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