UNPKG

@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
"use strict"; 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