@revrag-ai/embed-react-native
Version:
A powerful React Native library for integrating AI-powered voice agents into mobile applications. Features real-time voice communication, intelligent speech processing, customizable UI components, and comprehensive event handling for building conversation
299 lines (284 loc) • 12.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.EmbedButton = EmbedButton;
exports.default = void 0;
var _lottieReactNative = _interopRequireDefault(require("lottie-react-native"));
var _react = require("react");
var _reactNative = require("react-native");
var _reactNativeGestureHandler = require("react-native-gesture-handler");
var _reactNativeLinearGradient = _interopRequireDefault(require("react-native-linear-gradient"));
var _voiceagent = require("../../hooks/voiceagent.js");
var _EmbedButtonStyle = require("../styles/EmbedButton.style.js");
var _EmbedVoice = _interopRequireDefault(require("./EmbedVoice.js"));
var _EmbedAudioWave = require("./EmbedAudioWave.js");
var _embedEvent = _interopRequireWildcard(require("../../events/embed.event.js"));
var _EmbedButtonHelpers = require("../../hooks/EmbedButton.helpers.js");
var _EmbedButtonHooks = require("../../hooks/EmbedButton.hooks.js");
var _EmbedButtonAnimations = require("../../hooks/EmbedButton.animations.js");
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* @file EmbedButton.tsx
* @description A customizable floating action button component for voice agent interactions.
* Features: draggable, expandable, animated, with call controls and auto-trigger support.
*/
// Helpers and constants
// Custom hooks
// Animation hooks
// ==================== STYLES CONFIG ====================
const defaultStyles = {
buttonWidth: _EmbedButtonHelpers.BUTTON_DIMENSIONS.WIDTH,
buttonHeight: _EmbedButtonHelpers.BUTTON_DIMENSIONS.HEIGHT,
borderRadius: 100,
marginBottom: 20,
spacing: {
SMALL: 10,
MEDIUM: 15,
LARGE: 25,
EXTRA_SMALL: 5,
EXTRA_LARGE: 35,
EXTRA_EXTRA_LARGE: 45
}
};
// ==================== MAIN COMPONENT ====================
/**
* EmbedButton - Main voice agent floating action button
*
* @example
* ```tsx
* <EmbedButton />
* ```
*/
function EmbedButton() {
// ==================== VOICE AGENT STATE ====================
const {
initializeVoiceAgent,
tokenDetails,
endCall,
isLoading,
isMicMuted,
muteMic,
unmuteMic,
connectionState,
roomRef
} = (0, _voiceagent.useVoiceAgent)();
// ==================== LOCAL STATE ====================
const [isOpen, setIsOpen] = (0, _react.useState)(false);
const lottieRef = (0, _react.useRef)(null);
const styles = (0, _react.useMemo)(() => (0, _EmbedButtonStyle.createEmbedButtonStyles)(defaultStyles), []);
// ==================== CUSTOM HOOKS ====================
const configData = (0, _EmbedButtonHooks.useConfigData)();
const {
callDuration,
resetDuration
} = (0, _EmbedButtonHooks.useCallDuration)(connectionState);
const {
handleStartCall,
handleEndCall,
handleMicToggle
} = (0, _EmbedButtonHooks.useCallManagement)({
initializeVoiceAgent,
endCall,
muteMic,
unmuteMic,
isMicMuted,
resetDuration,
setIsOpen
});
const {
isAutoOpen,
setIsAutoOpen
} = (0, _EmbedButtonHooks.useInactivityBehavior)({
configData,
isOpen,
isLoading,
hasActiveToken: !!tokenDetails?.token,
onStartCall: handleStartCall
});
// ==================== ANIMATIONS ====================
const animationValues = (0, _EmbedButtonAnimations.useAnimationValues)();
const {
isPressed,
offset,
start,
menuAnimation,
buttonWidth,
buttonScale
} = animationValues;
(0, _EmbedButtonAnimations.useButtonAnimations)(isOpen, menuAnimation, buttonWidth);
(0, _EmbedButtonAnimations.useBreathingAnimation)(isOpen, isAutoOpen, buttonScale);
const buttonAnimatedStyles = (0, _EmbedButtonAnimations.useButtonAnimatedStyles)(isOpen, offset, buttonWidth, isPressed, buttonScale);
const popupAnimatedStyles = (0, _EmbedButtonAnimations.usePopupAnimatedStyles)(offset, isPressed);
const panGesture = (0, _react.useMemo)(() => (0, _EmbedButtonAnimations.createPanGesture)(isPressed, offset, start, isOpen, setIsAutoOpen), [isPressed, offset, start, isOpen, setIsAutoOpen]);
// ==================== AGENT EVENT EMISSIONS ====================
// Emit agent connected/disconnected events based on connection state
(0, _react.useEffect)(() => {
const emitConnectionEvent = async () => {
if (connectionState === 'connected') {
await _embedEvent.default.event.emit(_embedEvent.AgentEvent.AGENT_CONNECTED, {
timestamp: new Date().toISOString(),
metadata: {
callDuration: 0
}
});
} else if (connectionState === 'disconnected' && callDuration > 0) {
// Only emit disconnected if we were previously connected
await _embedEvent.default.event.emit(_embedEvent.AgentEvent.AGENT_DISCONNECTED, {
timestamp: new Date().toISOString(),
metadata: {
callDuration
}
});
}
};
emitConnectionEvent().catch(error => {
console.error('Error emitting connection event:', error);
});
}, [connectionState, callDuration]);
// Emit popup visibility events when isAutoOpen changes
(0, _react.useEffect)(() => {
const emitPopupEvent = async () => {
await _embedEvent.default.event.emit(_embedEvent.AgentEvent.POPUP_MESSAGE_VISIBLE, {
value: isAutoOpen,
metadata: {
trigger: isAutoOpen ? 'auto_inactivity' : 'manual_dismiss'
}
});
};
emitPopupEvent().catch(error => {
console.error('Error emitting popup visibility event:', error);
});
}, [isAutoOpen]);
// ==================== HANDLERS ====================
const handleButtonPress = () => {
setIsOpen(!isOpen);
setIsAutoOpen(false);
};
const handleConnected = () => {
// Hook for handling successful connection
};
// ==================== COMPUTED VALUES ====================
const lottieSource = (0, _react.useMemo)(() => ({
uri: configData?.icon_animation || _EmbedButtonHelpers.ICON_URLS.AMPLIFY_ANIMATION
}), [configData?.icon_animation]);
const statusText = (0, _react.useMemo)(() => {
if (isLoading) return 'Connecting...';
if (tokenDetails?.token) return (0, _EmbedButtonHelpers.formatDuration)(callDuration);
return configData?.agent_type || 'Onboarding Agent';
}, [isLoading, tokenDetails?.token, callDuration, configData?.agent_type]);
const gradientColors = configData?.gradient || _EmbedButtonHelpers.DEFAULT_GRADIENT_COLORS;
const agentName = configData?.agent_name || 'Your AI Agent';
const popupText = configData?.popup_description || 'Any doubts? Ask agent now';
const connectButtonText = configData?.connect_button_title || 'Start Call';
// ==================== EARLY RETURNS ====================
if (!configData) return null;
// ==================== RENDER ====================
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.container,
children: [isAutoOpen && !isOpen && /*#__PURE__*/(0, _jsxRuntime.jsx)(_EmbedButtonAnimations.Animated.View, {
style: [popupAnimatedStyles, styles.popupContainer],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.popupText,
children: popupText
})
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureDetector, {
gesture: panGesture,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_EmbedButtonAnimations.Animated.View, {
pointerEvents: "auto",
style: [styles.button, buttonAnimatedStyles, styles.buttonContent],
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeLinearGradient.default, {
colors: gradientColors,
start: {
x: 0,
y: 0
},
end: {
x: 1,
y: 0
},
style: [styles.linearGradient, isOpen ? styles.expandedLinearGradient : styles.collapsedLinearGradient],
angle: 0,
angleCenter: {
x: 0.5,
y: 0.5
},
children: [tokenDetails?.token && /*#__PURE__*/(0, _jsxRuntime.jsx)(_EmbedVoice.default, {
url: tokenDetails.server_url,
token: tokenDetails.token,
onDisconnected: handleEndCall,
roomRef: roomRef,
onConnected: handleConnected
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
onPress: handleButtonPress,
style: styles.pressable,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_lottieReactNative.default, {
ref: lottieRef,
source: lottieSource,
autoPlay: true,
loop: true,
style: styles.iconImage,
enableMergePathsAndroidForKitKatAndAbove: true,
enableSafeModeAndroid: true
})
}), isOpen && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.expandedContentContainer,
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.leftContentSection,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.agentNameText, styles.leftAlignedText],
children: agentName
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.leftAlignedText, styles.statusText],
children: statusText
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.middleContentSection,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_EmbedAudioWave.WaveformVisualizer, {
roomRef: roomRef
})
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.rightContentSection,
children: [!tokenDetails?.token && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.buttonContainer,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
onPress: handleStartCall,
style: styles.startCallButton,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.startCallText,
children: connectButtonText
})
})
}), tokenDetails?.token && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.buttonContainer,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: styles.muteButton,
onPress: handleMicToggle,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
source: {
uri: isMicMuted ? _EmbedButtonHelpers.ICON_URLS.MIC_OFF : _EmbedButtonHelpers.ICON_URLS.MIC_ON
},
style: styles.buttonImage
})
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
onPress: handleEndCall,
style: styles.endCallButton,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
source: {
uri: _EmbedButtonHelpers.ICON_URLS.END_CALL
},
style: styles.buttonImage
})
})]
})]
})]
})]
})
})
})]
});
}
var _default = exports.default = EmbedButton;
//# sourceMappingURL=EmbedButton.js.map