UNPKG

@agentman/chat-widget

Version:

Agentman Chat Widget for easy integration with web applications

763 lines (622 loc) 22.4 kB
# Dynamic Prompts Feature ## Overview Dynamic prompts allow the chat widget to display different prompt suggestions based on the user's current location (URL) on the website. This feature is implemented in the core ChatWidget and works across all platforms (Shopify, WordPress, Wix, standalone). ## Status - **Phase 1**: URL-based prompts ✅ (Current) - **Phase 2**: Hash/section-based prompts 📋 (Future) - **Phase 3**: Scroll-based detection 📋 (Future) ## Architecture ### Core Implementation Dynamic prompts are implemented in the core ChatWidget (`assistantWidget/`) making them available to all platforms: ``` Core ChatWidget (PromptManager.ts) ↓ Platform Wrappers (Shopify, WordPress, Wix) ↓ User Configuration (JSON file or inline) ``` ### Key Components 1. **PromptManager** (`assistantWidget/PromptManager.ts`) - URL pattern matching engine - Priority-based rule resolution - Prompt resolution logic 2. **Type Definitions** (`assistantWidget/types/types.ts`) - PromptRule interface - ContentConfig with rules support 3. **ChatWidget Integration** (`assistantWidget/ChatWidget.ts`) - URL change detection (popstate event) - Debounced updates (100ms) - Automatic prompt updates ## How It Works ### Initial Load When the widget initializes: 1. PromptManager is created with rules from config 2. Current URL (`window.location.pathname`) is resolved against rules 3. Matching prompts are set in config **before** welcome screen is created 4. Welcome screen renders with correct prompts for the current page ### URL Changes When the user navigates (using browser history, links, or back/forward): 1. `popstate` event fires 2. Debounced handler (100ms) resolves new prompts 3. Config is updated with new prompts 4. UI updates based on current state: - **Collapsed state**: Typewriter text updates immediately - **Expanded state**: Both typewriter text and prompt buttons update - **Conversation view**: Prompts stored for next welcome screen transition ### New Conversation When clicking "New" to start a new conversation: 1. Current URL prompts are resolved 2. Config is updated with current prompts 3. Welcome screen is created with updated prompts 4. User sees prompts relevant to their current page ### Input-Bar-Sheet Mode Behavior - **Typewriter**: Always visible in collapsed state, updates immediately on URL change - **Prompt buttons**: Only visible when sheet is expanded, update when: - Sheet expands (after animation completes) - URL changes while expanded - New conversation is started ## Configuration Format ### JSON Structure ```json { "rules": [ { "urlPattern": "/", "matchType": "exact", "prompts": ["Have a product question", "Need to check on an order", "Help picking a product"] }, { "urlPattern": "/products", "matchType": "startsWith", "prompts": ["Sizing, features, or fit? Ask away.", "Have a product question", "Help picking a product"] }, { "urlPattern": "/collections", "matchType": "startsWith", "prompts": ["Browse products", "Filter by category", "Compare items"] }, { "urlPattern": "support", "matchType": "contains", "prompts": ["Order status, return or exchange? Ask away.", "", "Need to check on an order"] } ], "default": ["Have a product question", "Need to check on an order", "Help picking a product"] } ``` ### Rule Properties #### `urlPattern` (required) The URL pattern to match against `window.location.pathname` **Examples:** - `"/"` - Homepage only (with exact match) - `"/products"` - All product pages (with startsWith) - `"support"` - Any URL containing "support" (with contains) #### `matchType` (required) How to match the URL pattern: - **`exact`**: Pathname must exactly equal the pattern - Pattern: `"/"` matches `/` only - Does NOT match `/products` or `/about` - **`startsWith`**: Pathname must start with the pattern - Pattern: `"/products"` matches `/products`, `/products/shoe`, `/products/zen-neo-classic` - Does NOT match `/collections` - **`contains`**: Pathname must contain the pattern anywhere - Pattern: `"support"` matches `/support`, `/pages/support`, `/help-and-support` - Most flexible, use carefully #### `prompts` (required) Array of 1-3 prompt strings to display for this rule **Format:** `[string?, string?, string?]` **Examples:** ```json "prompts": ["Prompt 1", "Prompt 2", "Prompt 3"] "prompts": ["Only one prompt", "", ""] "prompts": ["Two prompts", "Second prompt", ""] ``` **Notes:** - Empty strings (`""`) hide that prompt slot - Maximum of 3 prompts supported - Order matters (displayed left-to-right or top-to-bottom) #### `default` (optional) Fallback prompts when no rules match ```json "default": ["Default 1", "Default 2", "Default 3"] ``` If not provided, falls back to static `data-prompt-1/2/3` attributes (Shopify) or config prompts. ## Matching Priority Rules are evaluated in this order: 1. **Exact match** - Check all rules with `matchType: "exact"` 2. **StartsWith match** - Check all rules with `matchType: "startsWith"` (longest pattern wins) 3. **Contains match** - Check all rules with `matchType: "contains"` (first match wins) 4. **Default prompts** - Use `default` from rules 5. **Static fallback** - Use platform-specific static prompts (e.g., `data-prompt-1/2/3`) ### Example Priority Resolution **Rules:** ```json { "rules": [ {"urlPattern": "/", "matchType": "exact", "prompts": ["Home 1", "Home 2", "Home 3"]}, {"urlPattern": "/products", "matchType": "startsWith", "prompts": ["Products 1", "Products 2", "Products 3"]}, {"urlPattern": "/products/zen", "matchType": "startsWith", "prompts": ["Zen 1", "Zen 2", "Zen 3"]}, {"urlPattern": "support", "matchType": "contains", "prompts": ["Support 1", "Support 2", "Support 3"]} ], "default": ["Default 1", "Default 2", "Default 3"] } ``` **URL Resolution:** - `/` → "Home 1", "Home 2", "Home 3" (exact match) - `/products` → "Products 1", "Products 2", "Products 3" (startsWith) - `/products/zen-neo-classic` → "Zen 1", "Zen 2", "Zen 3" (longer startsWith wins) - `/collections` → "Default 1", "Default 2", "Default 3" (no match, use default) - `/pages/support` → "Support 1", "Support 2", "Support 3" (contains match) ## Platform Integration ### Shopify Two configuration methods available: #### Method A: External JSON File (Recommended) **1. Create JSON file:** Create `assets/agentman-prompt-rules.json` in your Shopify theme: ```json { "rules": [ {"urlPattern": "/", "matchType": "exact", "prompts": ["Q1", "Q2", "Q3"]}, {"urlPattern": "/products", "matchType": "startsWith", "prompts": ["P1", "P2", "P3"]} ], "default": ["Default 1", "Default 2", "Default 3"] } ``` **2. Upload to Shopify:** - Admin → Settings → Files → Upload `agentman-prompt-rules.json` - Copy the file URL **3. Add to script tag:** ```liquid <script async src="https://storage.googleapis.com/chatwidget-shopify-storage-for-cdn/shopify/v4/widget.js" data-agent-token="YOUR_TOKEN" data-prompt-rules-url="{{ 'agentman-prompt-rules.json' | asset_url }}" ...other attributes... > </script> ``` **Advantages:** - Clean separation of config from HTML - Easy to update without touching Liquid code - Better for large rule sets - Can be version controlled separately #### Method B: Inline JSON **Add to script tag:** ```html <script async src="https://storage.googleapis.com/.../widget.js" data-agent-token="YOUR_TOKEN" data-prompt-rules='{"rules":[{"urlPattern":"/","matchType":"exact","prompts":["Q1","Q2","Q3"]}],"default":["D1","D2","D3"]}' ...other attributes... > </script> ``` **Advantages:** - No separate file needed - All config in one place **Disadvantages:** - Gets messy with many rules - Harder to read/maintain #### Shopify Liquid Examples **Example 1: Product pages get special prompts** ```liquid {% assign prompt_rules = '{"rules":[{"urlPattern":"/products","matchType":"startsWith","prompts":["Product question?","Need sizing help?","Compare products"]}],"default":["Ask anything","Help & Support","Browse catalog"]}' %} <script async src="..." data-prompt-rules='{{ prompt_rules }}' > </script> ``` **Example 2: Different prompts per template** ```liquid {% if template.name == 'product' %} {% assign prompt_url = 'product-prompts.json' %} {% elsif template.name == 'collection' %} {% assign prompt_url = 'collection-prompts.json' %} {% else %} {% assign prompt_url = 'default-prompts.json' %} {% endif %} <script async src="..." data-prompt-rules-url="{{ prompt_url | asset_url }}" > </script> ``` ### WordPress **Coming soon:** Admin panel UI for building prompt rules ```php // Save rules to post_meta $prompt_rules = get_post_meta($post_id, 'agentman_prompt_rules', true); // Pass to widget echo '<script> window.agentmanConfig = { content: { messagePrompts: { rules: ' . json_encode($prompt_rules) . ' } } }; </script>'; ``` ### Wix **Coming soon:** Settings panel integration ```javascript // Wix App Settings window.agentmanConfig = { content: { messagePrompts: { rules: [...] // From Wix settings } } }; ``` ### Standalone / Direct Usage ```javascript new ChatWidget({ api: { url: '...', token: '...' }, content: { messagePrompts: { rules: [ { urlPattern: '/products', matchType: 'startsWith', prompts: ['Product Q1', 'Product Q2', 'Product Q3'] }, { urlPattern: '/', matchType: 'exact', prompts: ['Home Q1', 'Home Q2', 'Home Q3'] } ], default: ['Default 1', 'Default 2', 'Default 3'] } } }); ``` ### Instapage Landing Pages ```html <!-- Embed in Instapage custom code --> <script async src="https://storage.googleapis.com/.../widget.js" data-agent-token="YOUR_TOKEN" data-prompt-rules='{"rules":[{"urlPattern":"landing","matchType":"contains","prompts":["Landing Q1","Landing Q2","Landing Q3"]}]}' > </script> ``` ## Configuration Priority When multiple config methods exist, priority is: 1. `data-prompt-rules-url` (external JSON file) 2. `data-prompt-rules` (inline JSON) 3. `data-prompt-1/2/3` (static prompts - backward compatible) 4. Widget defaults ## URL Change Detection The widget automatically detects URL changes via: - **popstate event**: Browser back/forward buttons - **Debouncing**: 100ms delay to prevent excessive updates - **Automatic updates**: Prompts update immediately when URL changes **Note:** Page reloads will also re-evaluate rules based on new URL. ## Real-World Examples ### Example 1: E-commerce Store (Nested Bean) **File:** `assets/agentman-prompt-rules.json` ```json { "rules": [ { "urlPattern": "/", "matchType": "exact", "prompts": ["Have a product question", "Need to check on an order", "Help picking a product"] }, { "urlPattern": "/products", "matchType": "startsWith", "prompts": ["Sizing, features, or fit? Ask away.", "Have a product question", "Help picking a product"] }, { "urlPattern": "/collections", "matchType": "startsWith", "prompts": ["Browse by category", "Filter products", "Compare items"] }, { "urlPattern": "support", "matchType": "contains", "prompts": ["Order status, return or exchange? Ask away.", "Track my order", "Need to check on an order"] }, { "urlPattern": "/cart", "matchType": "exact", "prompts": ["Payment options?", "Shipping questions?", "Apply discount code"] }, { "urlPattern": "/pages/about", "matchType": "exact", "prompts": ["Learn about our story", "Our mission", "Contact us"] } ], "default": ["Have a product question", "Need to check on an order", "Help picking a product"] } ``` ### Example 2: SaaS Product Site ```json { "rules": [ { "urlPattern": "/", "matchType": "exact", "prompts": ["See a demo", "Pricing questions", "Compare plans"] }, { "urlPattern": "/pricing", "matchType": "exact", "prompts": ["Which plan is right?", "Enterprise pricing", "Free trial available?"] }, { "urlPattern": "/features", "matchType": "startsWith", "prompts": ["Feature details", "Integration questions", "API documentation"] }, { "urlPattern": "/docs", "matchType": "startsWith", "prompts": ["Search documentation", "Getting started guide", "API reference"] } ], "default": ["How can we help?", "Contact support", "Schedule a call"] } ``` ### Example 3: Blog/Content Site ```json { "rules": [ { "urlPattern": "/blog", "matchType": "startsWith", "prompts": ["Find related articles", "Subscribe to newsletter", "Search blog"] }, { "urlPattern": "/guides", "matchType": "startsWith", "prompts": ["Download full guide", "Related tutorials", "Expert tips"] }, { "urlPattern": "/", "matchType": "exact", "prompts": ["What are you looking for?", "Popular articles", "Browse categories"] } ], "default": ["Search content", "Browse categories", "Contact us"] } ``` ## CSV to JSON Conversion **Your CSV format:** ```csv Location,Position,Prompt 1,Prompt 2,Prompt 3 Home page,/,Have a product question,Need to check on an order,Help picking a product PDP,/products,Sizing features or fit? Ask away.,Have a product question,Help picking a product Collections,/collections,Browse products,Filter by category,Compare items Support,support,Order status return or exchange? Ask away.,,Need to check on an order ``` **Convert to JSON manually (for now):** ```json { "rules": [ { "urlPattern": "/", "matchType": "exact", "prompts": ["Have a product question", "Need to check on an order", "Help picking a product"] }, { "urlPattern": "/products", "matchType": "startsWith", "prompts": ["Sizing, features, or fit? Ask away.", "Have a product question", "Help picking a product"] }, { "urlPattern": "/collections", "matchType": "startsWith", "prompts": ["Browse products", "Filter by category", "Compare items"] }, { "urlPattern": "support", "matchType": "contains", "prompts": ["Order status, return or exchange? Ask away.", "", "Need to check on an order"] } ], "default": ["Have a product question", "Need to check on an order", "Help picking a product"] } ``` **Conversion rules:** - Column "Position" → `urlPattern` - Determine `matchType` based on pattern: - `/` exactly → `"exact"` - Starts with `/` → `"startsWith"` - No leading `/` → `"contains"` - Empty CSV cells → empty string `""` in prompts array **Future:** Automated CSV converter tool ## Testing ### Test File: `test-dynamic-prompts.html` ```html <!DOCTYPE html> <html> <head> <title>Dynamic Prompts Test</title> </head> <body> <h1>Dynamic Prompts Test</h1> <nav> <a href="/">Home</a> | <a href="/products">Products</a> | <a href="/products/test">Product Detail</a> | <a href="/collections">Collections</a> | <a href="/pages/support">Support</a> </nav> <div id="chat-container"></div> <script src="dist/index.js"></script> <script> new AgentmanChatWidget.ChatWidget({ api: { url: 'YOUR_API_URL', token: 'YOUR_TOKEN' }, containerId: 'chat-container', content: { messagePrompts: { rules: [ {urlPattern: '/', matchType: 'exact', prompts: ['Home 1', 'Home 2', 'Home 3']}, {urlPattern: '/products', matchType: 'startsWith', prompts: ['Product 1', 'Product 2', 'Product 3']}, {urlPattern: 'support', matchType: 'contains', prompts: ['Support 1', 'Support 2', 'Support 3']} ], default: ['Default 1', 'Default 2', 'Default 3'] } } }); </script> </body> </html> ``` ### Testing Checklist - [ ] Home page shows correct prompts - [ ] Product pages show product prompts - [ ] Collections show collection prompts - [ ] Support pages show support prompts - [ ] Browser back/forward updates prompts - [ ] Invalid JSON gracefully falls back to defaults - [ ] Missing `default` falls back to static prompts - [ ] Empty prompts hide properly ## Troubleshooting ### Prompts not updating **Check:** 1. Are rules loaded? Console: `widget.config.content.messagePrompts.rules` 2. Is URL being detected? Add `console.log(window.location.pathname)` 3. Are patterns matching? Test in browser console: ```javascript const pathname = window.location.pathname; const pattern = '/products'; console.log(pathname.startsWith(pattern)); // Should be true for /products/* ``` ### JSON fetch failing (Shopify) **Check:** 1. File uploaded to Shopify? Admin → Settings → Files 2. URL correct? View source and check `data-prompt-rules-url` 3. CORS issues? Shopify CDN should allow same-origin 4. Network tab in DevTools shows fetch error? ### Prompts show but don't change **Check:** 1. Are you navigating with `<a>` links or SPA routing? 2. Does browser history change? (back button should work) 3. Is debouncing delaying updates? (should be 100ms) ### Empty prompts showing **Check:** 1. Are empty strings intentional? Use `""` to hide prompts 2. Are all three prompts required? No - use 1, 2, or 3 prompts ### Prompts don't appear after clicking "New" conversation **Fixed in v5.0.1+** If prompts appear on initial load but disappear when starting a new conversation, ensure you're using the latest version. This was caused by prompts not being re-resolved when transitioning from conversation view back to welcome screen. **Workaround for older versions:** Refresh the page to reload the widget with correct prompts. ### Prompts container missing from DOM **Fixed in v5.0.1+** The `.am-ibf-prompts` container is now always rendered (even if empty initially) to support dynamic updates. If you see "Prompts container not in DOM yet" warnings, update to the latest version. ### Sheet expanding state issues **Fixed in v5.0.1+** Prompts now correctly wait for the sheet to fully expand (`am-sheet-expanded` class) before updating buttons. The `am-sheet-expanding` animation state is handled properly. ## Future Enhancements (Phase 2) ### Hash/Section-Based Prompts **Goal:** Update prompts when user scrolls to sections or clicks anchor links **Example:** ```json { "rules": [ { "urlPattern": "/products", "matchType": "startsWith", "prompts": ["Product Q1", "Product Q2", "Product Q3"], "sections": [ { "anchor": "#reviews", "prompts": ["Question about reviews?", "Sizing help?", "Compare products"] }, { "anchor": "#sizing", "prompts": ["What size should I get?", "Size chart help", "Fit guide"] } ] } ] } ``` **Detection:** - Listen to `hashchange` event - Match `window.location.hash` against section anchors - Update prompts when section changes **Use cases:** - Product reviews section - Pricing table on landing pages - FAQ sections - Testimonials ## Performance Considerations - **Debouncing:** URL changes debounced at 100ms to prevent excessive re-evaluation - **Caching:** Resolved prompts cached per URL to avoid redundant calculations - **Lazy evaluation:** Rules only evaluated when URL changes - **JSON fetch:** External JSON fetched once on widget load, cached in memory - **Event listener management (v5.0.1+):** Proper cleanup of URL change listeners to prevent memory leaks - **Selective DOM updates:** Only prompt buttons are re-rendered on URL change, other elements remain intact - **Type-safe timers:** Proper TypeScript typing for setTimeout/clearTimeout prevents memory issues ## Migration Guide ### From Static Prompts **Before:** ```html <script data-prompt-1="Question 1" data-prompt-2="Question 2" data-prompt-3="Question 3" > ``` **After:** ```html <script data-prompt-rules='{"rules":[{"urlPattern":"/","matchType":"exact","prompts":["Question 1","Question 2","Question 3"]}]}' > ``` Or use external JSON file for cleaner code. **Backward Compatibility:** Static prompts still work! Rules take priority when provided. ## Best Practices 1. **Use external JSON files** for production (easier to maintain) 2. **Set meaningful defaults** - cover the most common pages 3. **Use startsWith for categories** - `/products` covers all product pages 4. **Use contains sparingly** - can match unintended URLs 5. **Test on all page types** - home, PDP, collections, cart, support 6. **Keep prompts short** - 3-5 words max 7. **Make prompts actionable** - start with verbs when possible 8. **Avoid duplicates** - don't repeat same prompt across different rules ## Changelog ### v5.0.1 (2025-01-16) **Bug Fixes:** - Fixed prompts not appearing after clicking "New" conversation - Fixed prompts container missing from DOM on initial render - Fixed event listener memory leaks in URL change handler - Fixed prompt button listeners being removed unintentionally during refresh - Fixed sheet expanding state handling for proper prompt updates **Improvements:** - Prompts now resolve on initial widget load before welcome screen creation - Prompts update correctly when transitioning from conversation to welcome view - Type-safe timer declarations for better TypeScript compatibility - Proper type guards for InputBarSheetWelcomeView detection - Always render prompts container (even if empty) for dynamic updates **Performance:** - Selective DOM updates - only prompt buttons are re-rendered - Proper event listener cleanup prevents memory leaks - Bound event handlers stored for correct removal ### v5.0.0 (2025-01-15) **Initial Release:** - URL-based dynamic prompts (Phase 1) - PromptManager with exact, startsWith, contains matching - Priority-based rule resolution - Shopify integration with external JSON and inline JSON support - Debounced URL change detection (100ms) - Caching for resolved prompts - Input-bar-sheet mode support with typewriter text and prompt buttons ## Support For questions or issues: - GitHub: https://github.com/agentman/chat-widget/issues - Documentation: See README.md - Shopify specific: See shopify/README.md --- **Last Updated:** 2025-10-15 **Version:** Phase 1 (URL-based prompts) **Status:** In Development