{/* Widget will appear at bottom-left of this container */}
);
}
```
***
## 🧩 EmbedProvider (Recommended) [#-embedprovider-recommended]
Instead of manually placing `` on every page, wrap your app once with `EmbedProvider` and let it handle widget visibility automatically.
**BEST PRACTICE:** Use `EmbedProvider` to automatically manage the EmbedButton based on the current route.
The `EmbedProvider` component:
* ✅ **Automatically tracks** route changes and shows/hides the widget
* ✅ **Conditionally renders** the EmbedButton based on the current page
* ✅ **Supports delays** before the button appears on a route
* ✅ **Supports visibility groups** for per-route delay, continuity, and position
* ✅ **Built-in component** - no need to create a custom provider
### Basic Setup [#basic-setup]
```tsx
import { EmbedProvider, useInitialize } from '@revrag-ai/embed-react';
import '@revrag-ai/embed-react/style.css';
function App() {
const { isInitialized } = useInitialize("your-api-key");
if (!isInitialized) return
Loading...
;
return (
{/* Your app / router */}
);
}
```
### Show on specific routes only [#show-on-specific-routes-only]
```tsx
{/* Widget only visible on the listed routes */}
```
### EmbedProvider Props [#embedprovider-props]
| Property | Type | Required | Description |
| ----------------------------- | -------------------------------------- | -------- | ------------------------------------------------------------ |
| `children` | ReactNode | ✅ | Your app or router |
| `appVersion` | string | ✅ | App version string used in analytics |
| `includeScreens` | string\[] | ❌ | Routes where the button appears. Omit to show on all routes |
| `matchMode` | 'exact' \| 'prefix' | ❌ | Route matching mode (default: 'exact') |
| `currentPath` | string | ❌ | Current route path (auto-detected if not provided) |
| `usePathHook` | () => string | ❌ | Custom hook for path detection (e.g., Next.js `usePathname`) |
| `embedButtonDelayMs` | number | ❌ | Delay (ms) before the button appears on a route |
| `embedButtonVisibilityConfig` | EmbedButtonVisibilityConfig | ❌ | Advanced per-group delay, continuity, and inset config |
| `embedButtonPosition` | 'top' \| 'bottom' \| 'left' \| 'right' | ❌ | Button position for fixed positioning |
Advanced usage guide →
***
## 🎨 Responsive Behavior [#-responsive-behavior]
### Fixed Positioning [#fixed-positioning]
* **Desktop (> 500px)**: Widget stays at specified position
* **Mobile (≤ 500px)**: Widget expands to full width with 1rem padding from edges
### Embedded Positioning [#embedded-positioning-1]
* **Wide parent (> 400px)**: Widget aligns to left/right based on `side` prop
* **Narrow parent (≤ 400px)**: Widget auto-centers with equal padding
***
## 🔧 API Reference [#-api-reference]
### EmbedButton Props [#embedbutton-props]
```tsx
interface EmbedButtonProps {
// Positioning mode
positioning?: 'fixed' | 'embedded'; // default: 'fixed'
// Position configuration (for fixed mode)
position?: {
top?: string;
bottom?: string;
left?: string;
right?: string;
};
// Easy positioning (for embedded mode)
side?: 'left' | 'right';
// Offset from bottom (useful for bottom navbars)
bottomOffset?: number; // in pixels
// Custom className
className?: string;
}
```
### Hooks [#hooks]
#### useInitialize [#useinitialize]
```tsx
const { isInitialized, error } = useInitialize(apiKey: string);
```
Initializes the SDK with your API key. Must be called before using any other SDK features.
#### useEmbed [#useembed]
```tsx
const { currentPath } = useEmbed();
```
Reads `currentPath` from `EmbedProvider` context. Useful for accessing the current route within your components.
#### useLiveKit [#uselivekit]
```tsx
const { connect, disconnect, toggleMute } = useLiveKit();
```
Provides programmatic control over voice calls:
* `connect()`: Initiate a voice call
* `disconnect()`: End the current voice call
* `toggleMute()`: Mute/unmute the microphone
#### useSSRSafe / useBrowserSafe [#usessrsafe--usebrowsersafe]
```tsx
const isMounted = useSSRSafe();
// or
const isMounted = useBrowserSafe();
```
SSR-safe mount detection hooks. Returns `true` when component is mounted on the client side. Useful for Next.js and other SSR frameworks to prevent hydration mismatches.
***
## 📡 Event Management [#-event-management]
The SDK provides a powerful event system for tracking user data, custom events, and listening to agent state changes.
### EventKeys [#eventkeys]
Available event types:
```tsx
import { EventKeys } from '@revrag-ai/embed-react';
EventKeys.USER_DATA // 'user_data' - User identification and profile data
EventKeys.CUSTOM_EVENT // 'custom_event' - Custom application events
EventKeys.AGENT_CONNECTED // 'agent_start' - Voice agent connection (auto-tracked)
EventKeys.AGENT_DISCONNECTED // 'agent_end' - Voice agent disconnection (auto-tracked)
EventKeys.ANALYTICS_DATA // 'analytics_data'
```
**Note**: Only `USER_DATA` , `CUSTOM_EVENT` and `ANALYTICS_DATA` are available for manual use. Agent connection events are automatically tracked by the SDK and can be listened to via callbacks.
***
### Sending Events with embedEvent API [#sending-events-with-embedevent-api]
The `embedEvent` object provides methods for sending events to track user data and custom application events.
#### Send User Data [#send-user-data]
```tsx
import { embedEvent, EventKeys } from '@revrag-ai/embed-react';
// Send user data
const response = await embedEvent.event({
eventKey: EventKeys.USER_DATA,
data: {
app_user_id: 'user-123',
email: 'user@example.com',
name: 'John Doe',
plan: 'premium'
}
});
if (response.success) {
console.log('User data sent successfully');
}
```
#### Send Custom Events [#send-custom-events]
```tsx
import { embedEvent, EventKeys } from '@revrag-ai/embed-react';
// Track custom application event
await embedEvent.event({
eventKey: EventKeys.CUSTOM_EVENT,
data: {
event_name: 'purchase_completed',
product_id: 'prod-123',
amount: 99.99
}
});
```
#### Send Analytics Events [#send-analytics-events]
```tsx
import { embedEvent, EventKeys } from '@revrag-ai/embed-react';
// Track custom application event
await embedEvent.event({
eventKey: EventKeys.ANALYTICS_DATA,
data: {
event_name: 'purchase_completed', // event_name is compulsory in analytics_data event
product_id: 'prod-123',
amount: 99.99
}
});
```
#### Event Method Signature [#event-method-signature]
```tsx
embedEvent.event(params: UpdateDataRequest): Promise
interface UpdateDataRequest {
eventKey: EventKey; // Event type from EventKeys
data: {
app_user_id?: string; // User ID (required for USER_DATA)
[key: string]: unknown; // Additional data
};
session_id?: string; // Optional session ID
}
interface ApiResponse {
success: boolean;
data?: unknown;
message?: string;
error?: string;
}
```
***
### Listening to Agent Events [#listening-to-agent-events]
Monitor voice agent connection status in real-time. **These events are automatically sent to your backend AND emitted locally** for you to listen to.
#### Available Event Types for Listening [#available-event-types-for-listening]
```tsx
import { EventKeys } from '@revrag-ai/embed-react';
// Available events for listening:
EventKeys.AGENT_CONNECTED // 'agent_start' - Voice agent connected
EventKeys.AGENT_DISCONNECTED // 'agent_end' - Voice agent disconnected
```
**Automatic Backend Sync:**
* Agent events are **automatically sent to your backend** with `app_user_id`
* Events are **also emitted locally** for real-time UI updates
* Backend receives all event data including timestamps and metadata
* No manual API calls needed - it's all handled automatically
#### Event Listener Methods [#event-listener-methods]
```tsx
import { embedEvent } from '@revrag-ai/embed-react';
// Add event listener
embedEvent.addCallback(callback);
// Remove event listener
embedEvent.removeCallback(callback);
```
#### Basic Event Listening Example [#basic-event-listening-example]
```tsx
import React, { useEffect } from 'react';
import { embedEvent, EventKeys, EmbedButton } from '@revrag-ai/embed-react';
function MyComponent() {
useEffect(() => {
// Define event handler
const handleAgentEvent = (event) => {
if (event.type === EventKeys.AGENT_CONNECTED) {
console.log('✅ Agent connected:', event.data);
// Update UI to show agent is available
}
if (event.type === EventKeys.AGENT_DISCONNECTED) {
console.log('❌ Agent disconnected:', event.data);
// Update UI to show agent is unavailable
}
};
// Register callback
embedEvent.addCallback(handleAgentEvent);
// Cleanup
return () => {
embedEvent.removeCallback(handleAgentEvent);
};
}, []);
return ;
}
```
#### Complete Agent Monitoring Example [#complete-agent-monitoring-example]
```tsx
import React, { useEffect, useState } from 'react';
import { embedEvent, EventKeys, EmbedButton } from '@revrag-ai/embed-react';
function VoiceAgentMonitor() {
const [agentStatus, setAgentStatus] = useState<'idle' | 'connected' | 'disconnected'>('idle');
const [agentIdentity, setAgentIdentity] = useState('');
const [connectionTime, setConnectionTime] = useState(null);
useEffect(() => {
const handleEvent = (event) => {
// Handle agent connection
if (event.type === EventKeys.AGENT_CONNECTED) {
console.log('✅ Agent connected:', event);
console.log('Identity:', event.data?.identity);
console.log('Metadata:', event.data?.metadata);
console.log('Timestamp:', event.timestamp);
setAgentStatus('connected');
setAgentIdentity(event.data?.identity || 'Unknown');
setConnectionTime(new Date(event.timestamp));
// Update UI - show green indicator, enable features, etc.
// Example: Show notification, start analytics timer
}
// Handle agent disconnection
if (event.type === EventKeys.AGENT_DISCONNECTED) {
console.log('❌ Agent disconnected:', event);
console.log('Identity:', event.data?.identity);
console.log('Metadata:', event.data?.metadata);
console.log('Timestamp:', event.timestamp);
setAgentStatus('disconnected');
// Calculate call duration if needed
if (connectionTime) {
const duration = Date.now() - connectionTime.getTime();
console.log('Call duration:', duration / 1000, 'seconds');
}
// Update UI - show gray indicator, disable features, etc.
// Example: Show feedback form, log analytics
}
};
// Register event listener
embedEvent.addCallback(handleEvent);
// Cleanup listener on unmount
return () => {
embedEvent.removeCallback(handleEvent);
};
}, [connectionTime]);
return (
);
}
```
### 6. Contextual Events Based on User Actions [#6-contextual-events-based-on-user-actions]
```tsx
import { embedEvent, EventKeys, EmbedButton, useInitialize } from '@revrag-ai/embed-react';
function ProductPage({ product }) {
const { isInitialized } = useInitialize("your-api-key");
const handleAddToCart = async () => {
// Send custom event when user adds to cart
await embedEvent.event({
eventKey: EventKeys.CUSTOM_EVENT,
data: {
event_name: 'product_added_to_cart',
product_id: product.id,
product_name: product.name,
price: product.price
}
});
};
return (
{isInitialized && }
);
}
```
***
## 🔄 Complete Integration Example [#-complete-integration-example]
Here's a complete example showing initialization, user tracking, event listening, and the widget all working together:
```tsx
import React, { useEffect, useState } from 'react';
import { EmbedButton, useInitialize, embedEvent, EventKeys } from '@revrag-ai/embed-react';
import '@revrag-ai/embed-react/style.css';
function App() {
const { isInitialized, isLoading, error } = useInitialize("your-api-key");
const [agentStatus, setAgentStatus] = useState('idle');
const [userDataSent, setUserDataSent] = useState(false);
// Initialize user data when SDK is ready
useEffect(() => {
if (isInitialized && !userDataSent) {
initializeUserData();
}
}, [isInitialized, userDataSent]);
// Listen to agent events
useEffect(() => {
const handleAgentEvent = (event) => {
if (event.type === EventKeys.AGENT_CONNECTED) {
console.log('Agent connected:', event);
setAgentStatus('connected');
// Show notification, enable features, etc.
}
if (event.type === EventKeys.AGENT_DISCONNECTED) {
console.log('Agent disconnected:', event);
setAgentStatus('disconnected');
// Show feedback form, log analytics, etc.
}
};
embedEvent.addCallback(handleAgentEvent);
return () => embedEvent.removeCallback(handleAgentEvent);
}, []);
const initializeUserData = async () => {
try {
// Send user data first (required)
await embedEvent.event({
eventKey: EventKeys.USER_DATA,
data: {
app_user_id: 'user-123',
email: 'user@example.com',
name: 'John Doe',
subscription: 'premium'
}
});
setUserDataSent(true);
} catch (error) {
console.error('Failed to initialize user data:', error);
}
};
// Send custom event on button click
const handlePurchase = async () => {
await embedEvent.event({
eventKey: EventKeys.CUSTOM_EVENT,
data: {
event_name: 'purchase_completed',
amount: 99.99,
product_id: 'prod-123'
}
});
};
// Handle loading states
if (isLoading) {
return
Initializing AI Assistant...
;
}
if (error) {
return
Error: {error}
;
}
if (!isInitialized || !userDataSent) {
return
Loading...
;
}
return (
My Application
Agent Status: {agentStatus}
{/* AI Widget - will appear at bottom-right */}
);
}
export default App;
```
### Key Points in This Example: [#key-points-in-this-example]
1. **✅ CSS Import**: Imported at the top of the file
2. **✅ SDK Initialization**: Using `useInitialize` hook with loading/error states
3. **✅ User Data**: Sent first before rendering the widget
4. **✅ Event Listeners**: Set up to monitor agent connection status
5. **✅ Custom Events**: Tracked when user performs actions
6. **✅ Widget Rendering**: Only rendered after successful initialization
***
## ⚡ Framework-Specific Notes [#-framework-specific-notes]
### React + Vite [#react--vite]
✅ Works perfectly with no additional configuration
### Next.js [#nextjs]
✅ Fully compatible with both App Router and Pages Router
⚠️ Import CSS in root layout or \_app file
### Create React App [#create-react-app]
✅ Works out of the box
⚠️ Import CSS in index.tsx
### Tailwind CSS Projects [#tailwind-css-projects]
✅ No conflicts! The library uses custom `embed-*` prefixed classes
✅ Your Tailwind styles won't affect the widget
✅ Widget styles won't affect your app
***
## 🐛 Troubleshooting [#-troubleshooting]
### Widget appears unstyled [#widget-appears-unstyled]
**Solution**: Make sure you've imported the CSS file:
```tsx
import '@revrag-ai/embed-react/style.css';
```
### Widget not appearing [#widget-not-appearing]
**Solution**: Ensure `useInitialize` has completed:
```tsx
const { isInitialized } = useInitialize("your-api-key");
if (!isInitialized) return
Loading...
;
```
### Widget overlaps with bottom navigation [#widget-overlaps-with-bottom-navigation]
**Solution**: Use the `bottomOffset` prop:
```tsx
```
### Widget too small on mobile [#widget-too-small-on-mobile]
**Solution**: This is handled automatically! The widget is fully responsive.
### Events not being sent [#events-not-being-sent]
**Solution**: Ensure you've sent USER\_DATA event first with `app_user_id`:
```tsx
await embedEvent.event({
eventKey: EventKeys.USER_DATA,
data: {
app_user_id: 'user-123',
// ... other data
}
});
```
### Agent event listeners not firing [#agent-event-listeners-not-firing]
**Solution**: Make sure callbacks are registered before agent connection:
```tsx
useEffect(() => {
const handleEvent = (event) => {
// Your event handling logic
};
embedEvent.addCallback(handleEvent);
return () => embedEvent.removeCallback(handleEvent);
}, []);
```
### "User identity not found" error [#user-identity-not-found-error]
**Solution**: Send USER\_DATA event before any other events:
```tsx
// ✅ Correct order
await embedEvent.event({ eventKey: EventKeys.USER_DATA, data: { app_user_id: 'user-123' } });
await embedEvent.event({ eventKey: EventKeys.CUSTOM_EVENT, data: { ... } });
// ❌ Wrong order
await embedEvent.event({ eventKey: EventKeys.CUSTOM_EVENT, data: { ... } }); // Error!
```
### Custom events being blocked [#custom-events-being-blocked]
**Solution**: Only `USER_DATA` and `CUSTOM_EVENT` are allowed for manual sending. Agent events (`AGENT_CONNECTED`, `AGENT_DISCONNECTED`) are auto-tracked and can only be listened to, not manually sent.
***
## 📋 Checklist [#-checklist]
Before deploying, ensure:
**Basic Setup:**
* [ ] CSS file is imported
* [ ] API key is configured
* [ ] `isInitialized` is checked before rendering
* [ ] Parent container has `position: relative` (for embedded mode)
* [ ] Parent container has sufficient height (for embedded mode)
* [ ] Bottom offset is set if you have bottom navigation
**Event System:**
* [ ] USER\_DATA event sent first with `app_user_id`
* [ ] USER\_DATA sent before rendering EmbedButton
* [ ] Event listeners registered before agent connection
* [ ] Event listeners cleaned up on component unmount
* [ ] Custom events include proper context (screen, flow)
**Production Readiness:**
* [ ] Error handling for failed event sends
* [ ] Loading states during SDK initialization
* [ ] Agent connection status displayed to users
* [ ] Analytics tracking for agent events
* [ ] Proper cleanup of callbacks on unmount
***
## 🆘 Support [#-support]
* 📧 Issues: [GitHub Issues](https://github.com/revrag-ai/embed-react/issues)
* 📖 Docs: [GitHub README](https://github.com/revrag-ai/embed-react)
* 💬 Discussions: [GitHub Discussions](https://github.com/revrag-ai/embed-react/discussions)
***
## 🎉 You're All Set! [#-youre-all-set]
The widget is now ready to use. It's:
* ✅ Fully responsive
* ✅ Framework-agnostic
* ✅ Tailwind-compatible
* ✅ Production-ready
* ✅ Mobile-optimized
* ✅ Real-time event tracking
* ✅ Voice agent monitoring
* ✅ User context aware
### Quick Reference [#quick-reference]
**Import everything you need:**
```tsx
import {
EmbedButton, // The main widget component
EmbedProvider, // Built-in provider for automatic widget management
useInitialize, // SDK initialization hook
embedEvent, // Event management API
EventKeys, // Event type constants
} from '@revrag-ai/embed-react';
import '@revrag-ai/embed-react/style.css';
```
**Initialize and track:**
```tsx
// 1. Initialize SDK
const { isInitialized } = useInitialize("your-api-key");
// 2. Send user data
await embedEvent.event({
eventKey: EventKeys.USER_DATA,
data: { app_user_id: 'user-123' }
});
// 3. Listen to agent events
embedEvent.addCallback((event) => {
if (event.type === EventKeys.AGENT_CONNECTED) {
console.log('Agent connected!');
}
});
// 4. Render widget
```
Happy coding! 🚀