@revrag-ai/embed-react-native 1.0.5 → 1.0.6
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.
- package/dist/commonjs/Event/onwid.js +70 -0
- package/dist/commonjs/NativeOnwid.js +5 -0
- package/dist/commonjs/button.json +1 -0
- package/dist/commonjs/component/OnwidButton.js +507 -0
- package/dist/commonjs/component/audiowave.js +153 -0
- package/dist/commonjs/component/voice.js +127 -0
- package/dist/commonjs/hooks/initialize.js +96 -0
- package/dist/commonjs/hooks/initialize.types.js +2 -0
- package/{lib/module → dist/commonjs}/hooks/initializelivekit.js +7 -4
- package/dist/commonjs/hooks/voiceAgent.js +353 -0
- package/dist/commonjs/hooks/voiceAgent.types.js +4 -0
- package/dist/commonjs/index.d.js +22 -0
- package/dist/commonjs/index.js +34 -0
- package/dist/commonjs/onwidApi/api.js +185 -0
- package/dist/commonjs/onwidApi/api.types.js +2 -0
- package/dist/commonjs/package.json +1 -0
- package/dist/commonjs/store.key.js +38 -0
- package/dist/commonjs/style/onwidButton.style.js +243 -0
- package/dist/commonjs/utils/reanimatedHelpers.js +94 -0
- package/dist/commonjs/utils/utils.js +2 -0
- package/dist/module/Event/onwid.js +70 -0
- package/dist/module/NativeOnwid.js +5 -0
- package/dist/module/button.json +1 -0
- package/dist/module/component/OnwidButton.js +507 -0
- package/dist/module/component/audiowave.js +153 -0
- package/dist/module/component/voice.js +127 -0
- package/dist/module/hooks/initialize.js +96 -0
- package/dist/module/hooks/initialize.types.js +2 -0
- package/dist/module/hooks/initializelivekit.js +17 -0
- package/dist/module/hooks/voiceAgent.js +353 -0
- package/dist/module/hooks/voiceAgent.types.js +4 -0
- package/dist/module/index.d.js +22 -0
- package/dist/module/index.js +34 -0
- package/dist/module/onwidApi/api.js +185 -0
- package/dist/module/onwidApi/api.types.js +2 -0
- package/dist/module/store.key.js +38 -0
- package/dist/module/style/onwidButton.style.js +243 -0
- package/dist/module/utils/reanimatedHelpers.js +94 -0
- package/dist/module/utils/utils.js +2 -0
- package/{lib → dist}/typescript/Event/onwid.d.ts +1 -0
- package/{lib → dist}/typescript/NativeOnwid.d.ts +1 -0
- package/{lib → dist}/typescript/component/OnwidButton.d.ts +1 -0
- package/{lib → dist}/typescript/component/audiowave.d.ts +1 -0
- package/{lib → dist}/typescript/component/voice.d.ts +1 -0
- package/{lib → dist}/typescript/hooks/initialize.d.ts +1 -0
- package/{lib → dist}/typescript/hooks/initialize.types.d.ts +1 -0
- package/{lib → dist}/typescript/hooks/initializelivekit.d.ts +1 -0
- package/{lib → dist}/typescript/hooks/voiceAgent.d.ts +1 -0
- package/{lib → dist}/typescript/hooks/voiceAgent.types.d.ts +1 -0
- package/{lib → dist}/typescript/index.d.ts +3 -3
- package/{lib → dist}/typescript/onwidApi/api.d.ts +1 -0
- package/{lib → dist}/typescript/onwidApi/api.types.d.ts +1 -0
- package/{lib → dist}/typescript/store.key.d.ts +1 -0
- package/{lib → dist}/typescript/style/onwidButton.style.d.ts +1 -0
- package/{lib → dist}/typescript/utils/reanimatedHelpers.d.ts +1 -0
- package/dist/typescript/utils/utils.d.ts +1 -0
- package/package.json +29 -21
- package/lib/index.d.ts +0 -77
- package/lib/module/Event/onwid.js +0 -74
- package/lib/module/NativeOnwid.js +0 -4
- package/lib/module/component/OnwidButton.js +0 -366
- package/lib/module/component/audiowave.js +0 -137
- package/lib/module/component/voice.js +0 -103
- package/lib/module/hooks/initialize.js +0 -92
- package/lib/module/hooks/initialize.types.js +0 -2
- package/lib/module/hooks/voiceAgent.js +0 -334
- package/lib/module/hooks/voiceAgent.types.js +0 -2
- package/lib/module/index.js +0 -61
- package/lib/module/onwidApi/api.js +0 -184
- package/lib/module/onwidApi/api.types.js +0 -2
- package/lib/module/store.key.js +0 -47
- package/lib/module/style/onwidButton.style.js +0 -230
- package/lib/module/utils/reanimatedHelpers.js +0 -87
- package/lib/module/utils/utils.js +0 -1
- package/lib/typescript/utils/utils.d.ts +0 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file OnwidButton.tsx
|
|
5
|
+
* @description A customizable floating action button component for React Native applications.
|
|
6
|
+
* This component provides a draggable, expandable button with animation support and gradient styling.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import LottieView from 'lottie-react-native';
|
|
10
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
11
|
+
import { Dimensions, Image, Text, TouchableOpacity, View } from 'react-native';
|
|
12
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
13
|
+
import LinearGradient from 'react-native-linear-gradient';
|
|
14
|
+
import Voice from '../component/voice';
|
|
15
|
+
import { useVoiceAgent } from '../hooks/voiceAgent';
|
|
16
|
+
import { getAgentData } from '../store.key';
|
|
17
|
+
import { createOnwidButtonStyles } from '../style/onwidButton.style';
|
|
18
|
+
import { getReanimatedAPI, showReanimatedSetupError } from '../utils/reanimatedHelpers';
|
|
19
|
+
import { WaveformVisualizer } from './audiowave';
|
|
20
|
+
|
|
21
|
+
// Get reanimated API with fallbacks
|
|
22
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
23
|
+
const {
|
|
24
|
+
useSharedValue,
|
|
25
|
+
useAnimatedStyle,
|
|
26
|
+
withTiming,
|
|
27
|
+
withSpring,
|
|
28
|
+
withRepeat,
|
|
29
|
+
withSequence,
|
|
30
|
+
runOnJS,
|
|
31
|
+
Easing,
|
|
32
|
+
Animated,
|
|
33
|
+
isAvailable: isReanimatedAvailable
|
|
34
|
+
} = getReanimatedAPI();
|
|
35
|
+
|
|
36
|
+
// Show warning if reanimated is not available
|
|
37
|
+
if (!isReanimatedAvailable) {
|
|
38
|
+
showReanimatedSetupError();
|
|
39
|
+
}
|
|
40
|
+
const {
|
|
41
|
+
width: SCREEN_WIDTH,
|
|
42
|
+
height: SCREEN_HEIGHT
|
|
43
|
+
} = Dimensions.get('window');
|
|
44
|
+
const BUTTON_WIDTH = 60;
|
|
45
|
+
const EXPANDED_WIDTH = SCREEN_WIDTH * 0.9;
|
|
46
|
+
const BUTTON_HEIGHT = 60;
|
|
47
|
+
const GRADIENT_COLORS = ['#1E0844', '#B391F3'];
|
|
48
|
+
|
|
49
|
+
// Define mic icons as base64 images for portability
|
|
50
|
+
const MIC_ON_ICON = 'https://revrag-dev.s3.ap-south-1.amazonaws.com/Avatars/Mute+button.png';
|
|
51
|
+
const MIC_OFF_ICON = 'https://revrag-dev.s3.ap-south-1.amazonaws.com/Avatars/unmute.png';
|
|
52
|
+
|
|
53
|
+
// Add end call icon
|
|
54
|
+
const END_CALL_ICON = 'https://revrag-dev.s3.ap-south-1.amazonaws.com/Avatars/end+button.png';
|
|
55
|
+
const AMPLIFY_ANIMATION = 'https://revrag-dev.s3.ap-south-1.amazonaws.com/Avatars/amplify.json';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Default styles configuration for the button
|
|
59
|
+
*/
|
|
60
|
+
const defaultStyles = {
|
|
61
|
+
buttonWidth: 60,
|
|
62
|
+
buttonHeight: 60,
|
|
63
|
+
borderRadius: 100,
|
|
64
|
+
marginBottom: 20,
|
|
65
|
+
spacing: {
|
|
66
|
+
SMALL: 10,
|
|
67
|
+
MEDIUM: 15,
|
|
68
|
+
LARGE: 25
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* OnwidButton Component
|
|
74
|
+
*
|
|
75
|
+
* A floating action button that can be dragged around the screen and expanded to show additional content.
|
|
76
|
+
* Features include:
|
|
77
|
+
* - Draggable functionality
|
|
78
|
+
* - Expandable menu
|
|
79
|
+
* - Animated transitions
|
|
80
|
+
* - Gradient background
|
|
81
|
+
* - Customizable styling
|
|
82
|
+
*
|
|
83
|
+
* @component
|
|
84
|
+
* @example
|
|
85
|
+
* ```tsx
|
|
86
|
+
* <OnwidButton
|
|
87
|
+
* isOpen={false}
|
|
88
|
+
* onPress={(isOpen) => console.log('Button pressed:', isOpen)}
|
|
89
|
+
* menuComponent={<YourMenuComponent />}
|
|
90
|
+
* />
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export function OnwidButton() {
|
|
94
|
+
const {
|
|
95
|
+
initializeVoiceAgent,
|
|
96
|
+
tokenDetails,
|
|
97
|
+
endCall,
|
|
98
|
+
isLoading,
|
|
99
|
+
isMicMuted,
|
|
100
|
+
muteMic,
|
|
101
|
+
unmuteMic,
|
|
102
|
+
connectionState,
|
|
103
|
+
roomRef
|
|
104
|
+
} = useVoiceAgent();
|
|
105
|
+
// State management
|
|
106
|
+
const [configData, setConfigData] = useState(null);
|
|
107
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
108
|
+
const [callDuration, setCallDuration] = useState(0);
|
|
109
|
+
const timerRef = useRef(null);
|
|
110
|
+
const lottieRef = useRef(null);
|
|
111
|
+
console.log('roomRef', roomRef.current?.localParticipant);
|
|
112
|
+
|
|
113
|
+
// Animation values
|
|
114
|
+
const isPressed = useSharedValue(false);
|
|
115
|
+
const offset = useSharedValue({
|
|
116
|
+
x: 0,
|
|
117
|
+
y: 0
|
|
118
|
+
});
|
|
119
|
+
const start = useSharedValue({
|
|
120
|
+
x: 0,
|
|
121
|
+
y: 0
|
|
122
|
+
});
|
|
123
|
+
const menuAnimation = useSharedValue(0);
|
|
124
|
+
const buttonWidth = useSharedValue(BUTTON_WIDTH);
|
|
125
|
+
const buttonScale = useSharedValue(1);
|
|
126
|
+
// Styles
|
|
127
|
+
const styles = createOnwidButtonStyles(defaultStyles);
|
|
128
|
+
const [isAutoOpen, setIsAutoOpen] = useState(false);
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
const autoOpenTimer = setTimeout(() => {
|
|
131
|
+
if (!isOpen) {
|
|
132
|
+
console.log('autoOpenTimer', isOpen);
|
|
133
|
+
setIsAutoOpen(true);
|
|
134
|
+
}
|
|
135
|
+
}, 15000); // 15 seconds
|
|
136
|
+
|
|
137
|
+
return () => {
|
|
138
|
+
clearTimeout(autoOpenTimer);
|
|
139
|
+
};
|
|
140
|
+
}, [isOpen]);
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Fetch agent configuration data
|
|
144
|
+
*/
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
const fetchAgentData = async () => {
|
|
147
|
+
try {
|
|
148
|
+
const data = await getAgentData('@config_data');
|
|
149
|
+
setConfigData(data?.ui_config);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('Error retrieving agent data:', error);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
fetchAgentData();
|
|
155
|
+
}, []);
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Set up a timer to track call duration when connected
|
|
159
|
+
*/
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
if (connectionState === 'connected' && !timerRef.current) {
|
|
162
|
+
timerRef.current = setInterval(() => {
|
|
163
|
+
setCallDuration(prev => prev + 1);
|
|
164
|
+
}, 1000);
|
|
165
|
+
} else if (connectionState !== 'connected' && timerRef.current) {
|
|
166
|
+
clearInterval(timerRef.current);
|
|
167
|
+
timerRef.current = null;
|
|
168
|
+
|
|
169
|
+
// If we were previously connected and now disconnected, show an error
|
|
170
|
+
if (callDuration > 0) {
|
|
171
|
+
console.log('Call unexpectedly disconnected after', callDuration, 'seconds');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return () => {
|
|
175
|
+
if (timerRef.current) {
|
|
176
|
+
clearInterval(timerRef.current);
|
|
177
|
+
timerRef.current = null;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}, [connectionState, callDuration]);
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Handle menu animation and button width transitions
|
|
184
|
+
*/
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
menuAnimation.value = withTiming(isOpen ? 0.8 : 0, {
|
|
187
|
+
duration: 300
|
|
188
|
+
});
|
|
189
|
+
buttonWidth.value = withTiming(isOpen ? EXPANDED_WIDTH : BUTTON_WIDTH);
|
|
190
|
+
}, [isOpen, menuAnimation, buttonWidth]);
|
|
191
|
+
|
|
192
|
+
// Add breathing animation when button is closed but isAutoOpen is true
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (!isOpen && isAutoOpen) {
|
|
195
|
+
// Start breathing animation with faster speed
|
|
196
|
+
buttonScale.value = withRepeat(withSequence(withTiming(1.1, {
|
|
197
|
+
duration: 1500,
|
|
198
|
+
// Reduced from 1000ms to 600ms
|
|
199
|
+
easing: Easing.inOut(Easing.ease)
|
|
200
|
+
}), withTiming(1, {
|
|
201
|
+
duration: 1500,
|
|
202
|
+
// Reduced from 1000ms to 600ms
|
|
203
|
+
easing: Easing.inOut(Easing.ease)
|
|
204
|
+
})), -1,
|
|
205
|
+
// Infinite repeat
|
|
206
|
+
false // Don't reverse
|
|
207
|
+
);
|
|
208
|
+
} else {
|
|
209
|
+
// Reset animation
|
|
210
|
+
buttonScale.value = withTiming(1, {
|
|
211
|
+
duration: 300
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}, [isOpen, isAutoOpen]);
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Animated styles for the button
|
|
218
|
+
*/
|
|
219
|
+
const animatedStyles = useAnimatedStyle(() => {
|
|
220
|
+
const maxX = SCREEN_WIDTH - (isOpen ? EXPANDED_WIDTH : BUTTON_WIDTH) - 35;
|
|
221
|
+
const clampedX = Math.min(Math.max(offset.value.x, -maxX), 0);
|
|
222
|
+
return {
|
|
223
|
+
width: buttonWidth.value,
|
|
224
|
+
height: BUTTON_HEIGHT,
|
|
225
|
+
transform: [{
|
|
226
|
+
translateX: clampedX
|
|
227
|
+
}, {
|
|
228
|
+
translateY: offset.value.y
|
|
229
|
+
}, {
|
|
230
|
+
scale: withSpring(isPressed.value ? 0.95 : buttonScale.value)
|
|
231
|
+
}],
|
|
232
|
+
justifyContent: isOpen ? 'space-between' : 'flex-start',
|
|
233
|
+
overflow: 'hidden'
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Animated styles for the text
|
|
239
|
+
*/
|
|
240
|
+
const animatedTextStyles = useAnimatedStyle(() => {
|
|
241
|
+
const maxX = SCREEN_WIDTH;
|
|
242
|
+
const clampedX = Math.min(Math.max(offset.value.x, -maxX), 0);
|
|
243
|
+
return {
|
|
244
|
+
transform: [{
|
|
245
|
+
translateX: clampedX
|
|
246
|
+
}, {
|
|
247
|
+
translateY: offset.value.y
|
|
248
|
+
}, {
|
|
249
|
+
scale: withSpring(isPressed.value ? 1 : 1)
|
|
250
|
+
}]
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Pan gesture handler for drag functionality
|
|
256
|
+
*/
|
|
257
|
+
const gesture = Gesture.Pan().onBegin(() => {
|
|
258
|
+
isPressed.value = true;
|
|
259
|
+
if (isAutoOpen) {
|
|
260
|
+
runOnJS(setIsAutoOpen)(false);
|
|
261
|
+
}
|
|
262
|
+
}).onUpdate(e => {
|
|
263
|
+
const maxX = SCREEN_WIDTH - (isOpen ? EXPANDED_WIDTH : BUTTON_WIDTH) - 0;
|
|
264
|
+
const newX = Math.min(Math.max(e.translationX + start.value.x, -maxX), 0);
|
|
265
|
+
const maxY = SCREEN_HEIGHT - 150;
|
|
266
|
+
const newY = Math.min(Math.max(e.translationY + start.value.y, -maxY), 0);
|
|
267
|
+
offset.value = {
|
|
268
|
+
x: newX,
|
|
269
|
+
y: newY
|
|
270
|
+
};
|
|
271
|
+
}).onEnd(() => {
|
|
272
|
+
start.value = {
|
|
273
|
+
x: offset.value.x,
|
|
274
|
+
y: offset.value.y
|
|
275
|
+
};
|
|
276
|
+
}).onFinalize(() => {
|
|
277
|
+
isPressed.value = false;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Handle button press events
|
|
282
|
+
*/
|
|
283
|
+
const handlePress = () => {
|
|
284
|
+
console.log('handlePress', isOpen);
|
|
285
|
+
console.log('isAutoOpen', isAutoOpen);
|
|
286
|
+
// cleanup();
|
|
287
|
+
setIsOpen(!isOpen);
|
|
288
|
+
setIsAutoOpen(false);
|
|
289
|
+
};
|
|
290
|
+
const handleStartCall = async () => {
|
|
291
|
+
setCallDuration(0);
|
|
292
|
+
await initializeVoiceAgent();
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Render the button icon/animation
|
|
297
|
+
*/
|
|
298
|
+
|
|
299
|
+
const remoteSource = useMemo(() => ({
|
|
300
|
+
uri: configData?.icon_animation || AMPLIFY_ANIMATION
|
|
301
|
+
}), [configData?.icon_animation]);
|
|
302
|
+
const renderIcon = () => {
|
|
303
|
+
// When isAutoOpen is true, we don't play the Lottie animation
|
|
304
|
+
return /*#__PURE__*/_jsx(View, {
|
|
305
|
+
children: /*#__PURE__*/_jsx(LottieView, {
|
|
306
|
+
ref: lottieRef,
|
|
307
|
+
source: remoteSource,
|
|
308
|
+
autoPlay: true,
|
|
309
|
+
loop: true,
|
|
310
|
+
style: styles.iconImage,
|
|
311
|
+
enableMergePathsAndroidForKitKatAndAbove: true,
|
|
312
|
+
enableSafeModeAndroid: true
|
|
313
|
+
})
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
const handleConnected = () => {
|
|
317
|
+
console.log('Call connected');
|
|
318
|
+
};
|
|
319
|
+
const handleEndCall = async () => {
|
|
320
|
+
setIsOpen(false);
|
|
321
|
+
if (timerRef.current) {
|
|
322
|
+
clearInterval(timerRef.current);
|
|
323
|
+
timerRef.current = null;
|
|
324
|
+
}
|
|
325
|
+
setCallDuration(0);
|
|
326
|
+
await endCall();
|
|
327
|
+
};
|
|
328
|
+
const handleMicToggle = () => {
|
|
329
|
+
if (isMicMuted) {
|
|
330
|
+
unmuteMic();
|
|
331
|
+
} else {
|
|
332
|
+
muteMic();
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Format duration to MM:SS
|
|
337
|
+
const formatDuration = seconds => {
|
|
338
|
+
const minutes = Math.floor(seconds / 60);
|
|
339
|
+
const remainingSeconds = seconds % 60;
|
|
340
|
+
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// Get the status text based on current state
|
|
344
|
+
const getStatusText = () => {
|
|
345
|
+
if (isLoading) {
|
|
346
|
+
return 'Connecting...';
|
|
347
|
+
} else if (tokenDetails?.token) {
|
|
348
|
+
return `${formatDuration(callDuration)}`;
|
|
349
|
+
} else {
|
|
350
|
+
return configData?.agent_type || 'Onboarding Agent';
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
if (!configData) return null;
|
|
354
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
355
|
+
style: styles.container,
|
|
356
|
+
children: [isAutoOpen && !isOpen && /*#__PURE__*/_jsx(Animated.View, {
|
|
357
|
+
style: [animatedTextStyles, {
|
|
358
|
+
position: 'absolute',
|
|
359
|
+
borderRadius: 5,
|
|
360
|
+
paddingVertical: 2,
|
|
361
|
+
paddingHorizontal: 10,
|
|
362
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
363
|
+
bottom: BUTTON_HEIGHT + 40
|
|
364
|
+
// right: Math.abs(offset.value.x) + BUTTON_WIDTH + 0,
|
|
365
|
+
}],
|
|
366
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
367
|
+
style: {
|
|
368
|
+
color: 'white',
|
|
369
|
+
fontSize: 10,
|
|
370
|
+
fontWeight: '500'
|
|
371
|
+
},
|
|
372
|
+
children: configData?.popup_description || 'Revrag'
|
|
373
|
+
})
|
|
374
|
+
}), /*#__PURE__*/_jsx(GestureDetector, {
|
|
375
|
+
gesture: gesture,
|
|
376
|
+
children: /*#__PURE__*/_jsx(Animated.View, {
|
|
377
|
+
style: [styles.button, animatedStyles, styles.buttonContent, {
|
|
378
|
+
pointerEvents: 'auto'
|
|
379
|
+
}],
|
|
380
|
+
children: /*#__PURE__*/_jsxs(LinearGradient, {
|
|
381
|
+
colors: configData?.gradient || GRADIENT_COLORS,
|
|
382
|
+
start: {
|
|
383
|
+
x: 0,
|
|
384
|
+
y: 0
|
|
385
|
+
},
|
|
386
|
+
end: {
|
|
387
|
+
x: 1,
|
|
388
|
+
y: 0
|
|
389
|
+
},
|
|
390
|
+
style: [styles.linearGradient, {
|
|
391
|
+
width: '100%',
|
|
392
|
+
flexDirection: 'row',
|
|
393
|
+
alignItems: 'center',
|
|
394
|
+
paddingHorizontal: 0,
|
|
395
|
+
paddingLeft: 0,
|
|
396
|
+
paddingRight: isOpen ? 5 : 0
|
|
397
|
+
}],
|
|
398
|
+
angle: 0,
|
|
399
|
+
angleCenter: {
|
|
400
|
+
x: 0.5,
|
|
401
|
+
y: 0.5
|
|
402
|
+
},
|
|
403
|
+
children: [tokenDetails?.token && /*#__PURE__*/_jsx(Voice, {
|
|
404
|
+
url: tokenDetails?.server_url,
|
|
405
|
+
token: tokenDetails?.token,
|
|
406
|
+
onDisconnected: handleEndCall,
|
|
407
|
+
roomRef: roomRef,
|
|
408
|
+
onConnected: handleConnected
|
|
409
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
410
|
+
style: [styles.rowContainer, {
|
|
411
|
+
flexShrink: 0,
|
|
412
|
+
width: BUTTON_WIDTH,
|
|
413
|
+
padding: 0,
|
|
414
|
+
margin: 0
|
|
415
|
+
}],
|
|
416
|
+
children: /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
417
|
+
onPress: handlePress,
|
|
418
|
+
style: styles.pressable,
|
|
419
|
+
children: renderIcon()
|
|
420
|
+
})
|
|
421
|
+
}), isOpen && /*#__PURE__*/_jsxs(View, {
|
|
422
|
+
style: {
|
|
423
|
+
flex: 1,
|
|
424
|
+
flexDirection: 'row',
|
|
425
|
+
height: BUTTON_HEIGHT - 10,
|
|
426
|
+
marginLeft: 0,
|
|
427
|
+
marginRight: 0
|
|
428
|
+
},
|
|
429
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
430
|
+
style: {
|
|
431
|
+
flex: 1,
|
|
432
|
+
justifyContent: 'center',
|
|
433
|
+
alignItems: 'flex-start',
|
|
434
|
+
paddingLeft: 8,
|
|
435
|
+
paddingRight: 4
|
|
436
|
+
},
|
|
437
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
438
|
+
style: [styles.agentNameText, {
|
|
439
|
+
flexShrink: 1,
|
|
440
|
+
textAlign: 'left'
|
|
441
|
+
}],
|
|
442
|
+
children: configData?.agent_name || 'Revrag'
|
|
443
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
444
|
+
style: [styles.statusText, {
|
|
445
|
+
flexShrink: 1,
|
|
446
|
+
textAlign: 'left'
|
|
447
|
+
}],
|
|
448
|
+
children: getStatusText()
|
|
449
|
+
})]
|
|
450
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
451
|
+
style: {
|
|
452
|
+
flex: 1,
|
|
453
|
+
justifyContent: 'center',
|
|
454
|
+
alignItems: 'center',
|
|
455
|
+
paddingHorizontal: 4
|
|
456
|
+
},
|
|
457
|
+
children: /*#__PURE__*/_jsx(WaveformVisualizer, {
|
|
458
|
+
roomRef: roomRef
|
|
459
|
+
})
|
|
460
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
461
|
+
style: {
|
|
462
|
+
flex: 1,
|
|
463
|
+
justifyContent: 'center',
|
|
464
|
+
alignItems: 'flex-end',
|
|
465
|
+
paddingLeft: 4,
|
|
466
|
+
paddingRight: 8
|
|
467
|
+
},
|
|
468
|
+
children: [!tokenDetails?.token && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
469
|
+
onPress: handleStartCall,
|
|
470
|
+
style: styles.startCallButton,
|
|
471
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
472
|
+
style: styles.startCallText,
|
|
473
|
+
children: configData?.start_call_text || 'Start Call'
|
|
474
|
+
})
|
|
475
|
+
}), tokenDetails?.token && /*#__PURE__*/_jsxs(View, {
|
|
476
|
+
style: [styles.buttonContainer],
|
|
477
|
+
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
478
|
+
style: styles.muteButton,
|
|
479
|
+
onPress: handleMicToggle,
|
|
480
|
+
children: /*#__PURE__*/_jsx(Image, {
|
|
481
|
+
source: {
|
|
482
|
+
uri: isMicMuted ? MIC_OFF_ICON : MIC_ON_ICON
|
|
483
|
+
},
|
|
484
|
+
style: styles.buttonImage
|
|
485
|
+
})
|
|
486
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
487
|
+
onPress: handleEndCall,
|
|
488
|
+
style: styles.endCallButton,
|
|
489
|
+
children: /*#__PURE__*/_jsx(Image, {
|
|
490
|
+
source: {
|
|
491
|
+
uri: END_CALL_ICON
|
|
492
|
+
},
|
|
493
|
+
style: styles.buttonImage
|
|
494
|
+
})
|
|
495
|
+
})]
|
|
496
|
+
})]
|
|
497
|
+
})]
|
|
498
|
+
})]
|
|
499
|
+
})
|
|
500
|
+
})
|
|
501
|
+
})]
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Export default for easier imports
|
|
506
|
+
export default OnwidButton;
|
|
507
|
+
//# sourceMappingURL=OnwidButton.js.map
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { View, Animated } from 'react-native';
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
// React Native compatible waveform simulator
|
|
7
|
+
const useReactNativeAudioWaveform = roomRef => {
|
|
8
|
+
const [isAudioActive, setIsAudioActive] = useState(false);
|
|
9
|
+
const intervalRef = useRef(null);
|
|
10
|
+
const [currentHeights, setCurrentHeights] = useState(Array(10).fill(0));
|
|
11
|
+
|
|
12
|
+
// Create animated values for each bar
|
|
13
|
+
const barCount = 10;
|
|
14
|
+
const animatedBars = useRef(Array(barCount).fill(0).map(() => new Animated.Value(0))).current;
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
// Check if there's an active room connection AND if agent is talking
|
|
17
|
+
const checkAudioActivity = () => {
|
|
18
|
+
const room = roomRef.current;
|
|
19
|
+
if (room?.state !== 'connected') {
|
|
20
|
+
setIsAudioActive(false);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Check if any remote participant is currently speaking
|
|
25
|
+
let isAgentSpeaking = false;
|
|
26
|
+
|
|
27
|
+
// Loop through all remote participants
|
|
28
|
+
room.remoteParticipants.forEach(participant => {
|
|
29
|
+
const audioTrackPublications = Array.from(participant.getTrackPublications().values());
|
|
30
|
+
const remoteAudioTrack = audioTrackPublications.find(pub => pub.track?.kind === 'audio');
|
|
31
|
+
|
|
32
|
+
// Check if this participant has audio track, is not muted, and is actively speaking
|
|
33
|
+
if (remoteAudioTrack?.track && !remoteAudioTrack?.isMuted) {
|
|
34
|
+
// Check audio level to detect actual speech (optional but more accurate)
|
|
35
|
+
const audioLevel = participant.audioLevel || 0;
|
|
36
|
+
if (audioLevel > 0.05) {
|
|
37
|
+
// Threshold for detecting speech
|
|
38
|
+
isAgentSpeaking = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
setIsAudioActive(isAgentSpeaking);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Initial check
|
|
46
|
+
checkAudioActivity();
|
|
47
|
+
|
|
48
|
+
// Set up periodic checking for room state changes
|
|
49
|
+
intervalRef.current = setInterval(checkAudioActivity, 500);
|
|
50
|
+
|
|
51
|
+
// Clean up on unmount
|
|
52
|
+
return () => {
|
|
53
|
+
if (intervalRef.current) {
|
|
54
|
+
clearInterval(intervalRef.current);
|
|
55
|
+
intervalRef.current = null;
|
|
56
|
+
}
|
|
57
|
+
setIsAudioActive(false);
|
|
58
|
+
};
|
|
59
|
+
}, [roomRef]);
|
|
60
|
+
|
|
61
|
+
// Continuous smooth animation
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const animateWaveform = () => {
|
|
64
|
+
// Generate smooth waveform data - stop animation completely when not active
|
|
65
|
+
const targetHeights = isAudioActive ? Array(barCount).fill(0).map((_, index) => {
|
|
66
|
+
const timeOffset = Date.now() / 800 + index * 0.3;
|
|
67
|
+
const baseHeight = 0.5;
|
|
68
|
+
const amplitude = 0.5;
|
|
69
|
+
const height = baseHeight + amplitude * Math.abs(Math.sin(timeOffset));
|
|
70
|
+
return Math.max(0.1, Math.min(1.0, height));
|
|
71
|
+
}) : Array(barCount).fill(0); // Completely freeze animation when mic is muted
|
|
72
|
+
|
|
73
|
+
// Update current heights for conditional logic
|
|
74
|
+
setCurrentHeights(targetHeights);
|
|
75
|
+
const animations = animatedBars.map((animatedValue, index) => {
|
|
76
|
+
const targetHeight = targetHeights[index] || 0;
|
|
77
|
+
return Animated.timing(animatedValue, {
|
|
78
|
+
toValue: targetHeight,
|
|
79
|
+
duration: isAudioActive ? 400 : 600,
|
|
80
|
+
// Slower fade out when going inactive
|
|
81
|
+
useNativeDriver: false
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
Animated.parallel(animations).start();
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Start animation immediately and repeat
|
|
88
|
+
animateWaveform();
|
|
89
|
+
const animationInterval = setInterval(animateWaveform, 300);
|
|
90
|
+
return () => {
|
|
91
|
+
clearInterval(animationInterval);
|
|
92
|
+
};
|
|
93
|
+
}, [isAudioActive, animatedBars]);
|
|
94
|
+
return {
|
|
95
|
+
animatedBars,
|
|
96
|
+
currentHeights,
|
|
97
|
+
isActive: isAudioActive
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
export const WaveformVisualizer = ({
|
|
101
|
+
roomRef
|
|
102
|
+
}) => {
|
|
103
|
+
const {
|
|
104
|
+
animatedBars,
|
|
105
|
+
currentHeights,
|
|
106
|
+
isActive
|
|
107
|
+
} = useReactNativeAudioWaveform(roomRef);
|
|
108
|
+
console.log('animatedBars', animatedBars);
|
|
109
|
+
return /*#__PURE__*/_jsx(View, {
|
|
110
|
+
style: {
|
|
111
|
+
flexDirection: 'row',
|
|
112
|
+
alignItems: 'center',
|
|
113
|
+
height: '100%',
|
|
114
|
+
alignSelf: 'center',
|
|
115
|
+
// width: '100%',
|
|
116
|
+
// flex: 1,
|
|
117
|
+
justifyContent: 'center',
|
|
118
|
+
// position: 'absolute',
|
|
119
|
+
zIndex: 1000
|
|
120
|
+
},
|
|
121
|
+
children: animatedBars.map((animatedHeight, idx) => {
|
|
122
|
+
// Use the tracked height values instead of trying to access animated value directly
|
|
123
|
+
const currentHeightValue = currentHeights[idx] || 0.1;
|
|
124
|
+
|
|
125
|
+
// Apply conditional logic based on height
|
|
126
|
+
let conditionalValue;
|
|
127
|
+
if (currentHeightValue > 0.7) {
|
|
128
|
+
conditionalValue = 1;
|
|
129
|
+
} else if (currentHeightValue >= 0.4 && currentHeightValue <= 0.5) {
|
|
130
|
+
conditionalValue = 5;
|
|
131
|
+
} else {
|
|
132
|
+
conditionalValue = 1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// You can use conditionalValue for width, color, or other properties
|
|
136
|
+
return /*#__PURE__*/_jsx(Animated.View, {
|
|
137
|
+
style: {
|
|
138
|
+
width: conditionalValue === 10 ? 4 : 4,
|
|
139
|
+
borderRadius: 100,
|
|
140
|
+
// Example: wider bars for value 5
|
|
141
|
+
height: animatedHeight.interpolate({
|
|
142
|
+
inputRange: [0, 1],
|
|
143
|
+
outputRange: [0, 25]
|
|
144
|
+
}),
|
|
145
|
+
alignSelf: 'center',
|
|
146
|
+
backgroundColor: idx <= 1 || idx >= 8 ? 'rgba(255, 255, 255, 0.5)' : 'white',
|
|
147
|
+
margin: 1.5
|
|
148
|
+
}
|
|
149
|
+
}, idx);
|
|
150
|
+
})
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=audiowave.js.map
|