react-native-srschat 0.1.17 → 0.1.19
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/README.md +1 -0
- package/lib/commonjs/components/email.js +5 -4
- package/lib/commonjs/components/email.js.map +1 -1
- package/lib/commonjs/components/header.js +11 -20
- package/lib/commonjs/components/header.js.map +1 -1
- package/lib/commonjs/components/input.js +3 -2
- package/lib/commonjs/components/input.js.map +1 -1
- package/lib/commonjs/components/productCard.js +5 -1
- package/lib/commonjs/components/productCard.js.map +1 -1
- package/lib/commonjs/components/progressCircle.js +99 -0
- package/lib/commonjs/components/progressCircle.js.map +1 -0
- package/lib/commonjs/components/voice.js +87 -0
- package/lib/commonjs/components/voice.js.map +1 -0
- package/lib/commonjs/components/welcomeInput.js +3 -2
- package/lib/commonjs/components/welcomeInput.js.map +1 -1
- package/lib/commonjs/hooks/stream.js +9 -9
- package/lib/commonjs/hooks/stream.js.map +1 -1
- package/lib/commonjs/layout/layout.js +3 -3
- package/lib/commonjs/layout/layout.js.map +1 -1
- package/lib/commonjs/layout/window.js +111 -5
- package/lib/commonjs/layout/window.js.map +1 -1
- package/lib/commonjs/utils/audioRecorder.js +97 -0
- package/lib/commonjs/utils/audioRecorder.js.map +1 -0
- package/lib/commonjs/utils/textToSpeech.js +45 -0
- package/lib/commonjs/utils/textToSpeech.js.map +1 -0
- package/lib/module/components/email.js +5 -4
- package/lib/module/components/email.js.map +1 -1
- package/lib/module/components/header.js +11 -20
- package/lib/module/components/header.js.map +1 -1
- package/lib/module/components/input.js +3 -2
- package/lib/module/components/input.js.map +1 -1
- package/lib/module/components/productCard.js +4 -1
- package/lib/module/components/productCard.js.map +1 -1
- package/lib/module/components/progressCircle.js +90 -0
- package/lib/module/components/progressCircle.js.map +1 -0
- package/lib/module/components/voice.js +76 -0
- package/lib/module/components/voice.js.map +1 -0
- package/lib/module/components/welcomeInput.js +3 -2
- package/lib/module/components/welcomeInput.js.map +1 -1
- package/lib/module/hooks/stream.js +9 -9
- package/lib/module/hooks/stream.js.map +1 -1
- package/lib/module/layout/layout.js +3 -3
- package/lib/module/layout/layout.js.map +1 -1
- package/lib/module/layout/window.js +112 -6
- package/lib/module/layout/window.js.map +1 -1
- package/lib/module/utils/audioRecorder.js +88 -0
- package/lib/module/utils/audioRecorder.js.map +1 -0
- package/lib/module/utils/textToSpeech.js +34 -0
- package/lib/module/utils/textToSpeech.js.map +1 -0
- package/lib/typescript/components/input.d.ts.map +1 -1
- package/lib/typescript/components/productCard.d.ts.map +1 -1
- package/lib/typescript/components/progressCircle.d.ts +3 -0
- package/lib/typescript/components/progressCircle.d.ts.map +1 -0
- package/lib/typescript/components/voice.d.ts +3 -0
- package/lib/typescript/components/voice.d.ts.map +1 -0
- package/lib/typescript/components/welcomeInput.d.ts.map +1 -1
- package/lib/typescript/layout/window.d.ts.map +1 -1
- package/lib/typescript/utils/audioRecorder.d.ts +4 -0
- package/lib/typescript/utils/audioRecorder.d.ts.map +1 -0
- package/lib/typescript/utils/textToSpeech.d.ts +2 -0
- package/lib/typescript/utils/textToSpeech.d.ts.map +1 -0
- package/package.json +4 -3
- package/src/components/email.js +6 -6
- package/src/components/header.js +11 -10
- package/src/components/input.js +3 -1
- package/src/components/productCard.js +3 -0
- package/src/components/progressCircle.js +86 -0
- package/src/components/voice.js +84 -0
- package/src/components/welcomeInput.js +3 -1
- package/src/hooks/stream.js +2 -2
- package/src/layout/layout.js +3 -3
- package/src/layout/window.js +89 -4
- package/src/utils/audioRecorder.js +99 -0
- package/src/utils/textToSpeech.js +38 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useContext } from "react";
|
|
2
|
+
import { View, Animated, StyleSheet, Pressable, Text } from "react-native";
|
|
3
|
+
import { AppContext } from "../contexts/AppContext";
|
|
4
|
+
|
|
5
|
+
export const ProgressCircle = () => {
|
|
6
|
+
const { stopGenerating } = useContext(AppContext);
|
|
7
|
+
const spinValue = useRef(new Animated.Value(0)).current;
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const spinAnimation = Animated.loop(
|
|
11
|
+
Animated.timing(spinValue, {
|
|
12
|
+
toValue: 1,
|
|
13
|
+
duration: 1400,
|
|
14
|
+
useNativeDriver: true,
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
spinAnimation.start();
|
|
18
|
+
|
|
19
|
+
return () => spinAnimation.stop(); // Cleanup on unmount
|
|
20
|
+
}, [spinValue]);
|
|
21
|
+
|
|
22
|
+
const spin = spinValue.interpolate({
|
|
23
|
+
inputRange: [0, 1],
|
|
24
|
+
outputRange: ["0deg", "360deg"],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<Text style={styles.textBeta}>Beta version. Poseidon is learning!</Text>
|
|
30
|
+
<View style={styles.container}>
|
|
31
|
+
<Pressable style={styles.circleContainer} onPress={stopGenerating}>
|
|
32
|
+
<Animated.View style={[styles.circle, { transform: [{ rotate: spin }] }]} />
|
|
33
|
+
<View style={styles.stopSquare} />
|
|
34
|
+
</Pressable>
|
|
35
|
+
</View>
|
|
36
|
+
</>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const styles = StyleSheet.create({
|
|
41
|
+
container: {
|
|
42
|
+
flexDirection: "row",
|
|
43
|
+
justifyContent: "flex-end",
|
|
44
|
+
alignItems: "center",
|
|
45
|
+
paddingHorizontal: 6,
|
|
46
|
+
paddingVertical: 6,
|
|
47
|
+
backgroundColor: "#f6f6f6",
|
|
48
|
+
borderTopWidth: 1,
|
|
49
|
+
borderTopColor: "rgba(0, 0, 0, 0.1)",
|
|
50
|
+
paddingBottom: 35,
|
|
51
|
+
paddingTop: 15
|
|
52
|
+
},
|
|
53
|
+
circleContainer: {
|
|
54
|
+
justifyContent: "center",
|
|
55
|
+
alignItems: "center",
|
|
56
|
+
width: 50,
|
|
57
|
+
height: 50,
|
|
58
|
+
marginRight: 20,
|
|
59
|
+
position: "relative",
|
|
60
|
+
},
|
|
61
|
+
circle: {
|
|
62
|
+
borderWidth: 4,
|
|
63
|
+
borderColor: "#dddbd9",
|
|
64
|
+
borderTopColor: "#666666",
|
|
65
|
+
borderRadius: 50,
|
|
66
|
+
width: 40,
|
|
67
|
+
height: 40,
|
|
68
|
+
},
|
|
69
|
+
stopSquare: {
|
|
70
|
+
width: 13,
|
|
71
|
+
height: 13,
|
|
72
|
+
backgroundColor: "#666666",
|
|
73
|
+
position: "absolute",
|
|
74
|
+
top: "50%",
|
|
75
|
+
left: "50%",
|
|
76
|
+
transform: [{ translateX: -6.5 }, { translateY: -6.5 }],
|
|
77
|
+
},
|
|
78
|
+
textBeta: {
|
|
79
|
+
textAlign: 'center',
|
|
80
|
+
fontSize: 11,
|
|
81
|
+
color: '#808080',
|
|
82
|
+
fontWeight: '400',
|
|
83
|
+
marginTop: 5,
|
|
84
|
+
marginBottom: 2
|
|
85
|
+
},
|
|
86
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// VoiceButton.js
|
|
2
|
+
import React, { useState, useContext } from 'react';
|
|
3
|
+
import { TouchableOpacity, ActivityIndicator, View, StyleSheet } from 'react-native';
|
|
4
|
+
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
|
|
7
|
+
import { startRecording, stopRecording, requestAudioPermission } from '../utils/audioRecorder';
|
|
8
|
+
import { AppContext } from '../contexts/AppContext';
|
|
9
|
+
|
|
10
|
+
export const VoiceButton = () => {
|
|
11
|
+
const { data, handleVoiceSend } = useContext(AppContext);
|
|
12
|
+
const [isListening, setIsListening] = useState(false);
|
|
13
|
+
const [loading, setLoading] = useState(false);
|
|
14
|
+
|
|
15
|
+
const toggleRecording = async () => {
|
|
16
|
+
const hasPermission = await requestAudioPermission();
|
|
17
|
+
|
|
18
|
+
if (!hasPermission) {
|
|
19
|
+
console.error('Permission denied');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!isListening) {
|
|
24
|
+
startRecording(handleStopRecording);
|
|
25
|
+
setIsListening(true);
|
|
26
|
+
} else {
|
|
27
|
+
handleStopRecording();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleStopRecording = async () => {
|
|
32
|
+
setLoading(true);
|
|
33
|
+
const audioPath = await stopRecording();
|
|
34
|
+
const transcription = await transcribeAudio(audioPath);
|
|
35
|
+
setLoading(false);
|
|
36
|
+
setIsListening(false);
|
|
37
|
+
handleVoiceSend(audioPath, transcription);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const transcribeAudio = async (audioPath) => {
|
|
41
|
+
try {
|
|
42
|
+
const formData = new FormData();
|
|
43
|
+
formData.append('file', {
|
|
44
|
+
uri: audioPath,
|
|
45
|
+
type: 'audio/wav',
|
|
46
|
+
name: 'audio.wav',
|
|
47
|
+
});
|
|
48
|
+
formData.append('model', 'whisper-1');
|
|
49
|
+
|
|
50
|
+
const response = await axios.post(
|
|
51
|
+
'https://api.openai.com/v1/audio/transcriptions',
|
|
52
|
+
formData,
|
|
53
|
+
{
|
|
54
|
+
headers: {
|
|
55
|
+
Authorization: `Bearer ${data.openai_key}`,
|
|
56
|
+
'Content-Type': 'multipart/form-data',
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
return response.data.text;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Error transcribing audio:', error);
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<TouchableOpacity style={styles.button} onPress={toggleRecording} disabled={loading}>
|
|
69
|
+
{loading ? (
|
|
70
|
+
<ActivityIndicator size="small" color="#8E8E93" />
|
|
71
|
+
) : (
|
|
72
|
+
<Ionicons name={isListening ? 'stop-circle' : 'mic-outline'} size={24} color="#8E8E93" />
|
|
73
|
+
)}
|
|
74
|
+
</TouchableOpacity>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const styles = StyleSheet.create({
|
|
79
|
+
button: {
|
|
80
|
+
justifyContent: 'center',
|
|
81
|
+
alignItems: 'center',
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
@@ -14,7 +14,7 @@ import { AppContext } from '../contexts/AppContext';
|
|
|
14
14
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
15
15
|
|
|
16
16
|
export const WelcomeInput = ({ onProductCardClick, onAddToCartClick }) => {
|
|
17
|
-
const { handleSend, input, setInput, showModal, theme } = useContext(AppContext);
|
|
17
|
+
const { handleSend, input, setInput, showModal, theme, data } = useContext(AppContext);
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
20
|
<View style={styles.inputContainer}>
|
|
@@ -26,9 +26,11 @@ export const WelcomeInput = ({ onProductCardClick, onAddToCartClick }) => {
|
|
|
26
26
|
placeholderTextColor="#999"
|
|
27
27
|
multiline
|
|
28
28
|
/>
|
|
29
|
+
{data.openai_key &&
|
|
29
30
|
<TouchableOpacity style={styles.inputButton}>
|
|
30
31
|
<Ionicons name="mic-outline" size={24} color="#8E8E93" />
|
|
31
32
|
</TouchableOpacity>
|
|
33
|
+
}
|
|
32
34
|
<TouchableOpacity
|
|
33
35
|
style={styles.sendButton}
|
|
34
36
|
onPress={() => handleSend(input)}
|
package/src/hooks/stream.js
CHANGED
|
@@ -80,7 +80,7 @@ export function useWebSocketMessage() {
|
|
|
80
80
|
console.log(response)
|
|
81
81
|
}
|
|
82
82
|
switch (response.type) {
|
|
83
|
-
|
|
83
|
+
case 'middle_message':
|
|
84
84
|
const middleMessage = {
|
|
85
85
|
type: "middle",
|
|
86
86
|
text: response.message,
|
|
@@ -88,7 +88,7 @@ export function useWebSocketMessage() {
|
|
|
88
88
|
product_cards: "False",
|
|
89
89
|
}
|
|
90
90
|
setMessages([...messages, middleMessage])
|
|
91
|
-
break;
|
|
91
|
+
break;
|
|
92
92
|
case 'message':
|
|
93
93
|
if (response.product_cards == "False" || response.product_cards == false ) {
|
|
94
94
|
setGhostMessage(false);
|
package/src/layout/layout.js
CHANGED
|
@@ -133,12 +133,12 @@ const styles = StyleSheet.create({
|
|
|
133
133
|
left: 0,
|
|
134
134
|
right: 0,
|
|
135
135
|
bottom: 0,
|
|
136
|
-
top:
|
|
136
|
+
top: 0,
|
|
137
137
|
pointerEvents: 'box-none',
|
|
138
|
-
borderTopWidth: 1,
|
|
138
|
+
/* borderTopWidth: 1,
|
|
139
139
|
borderTopColor: '#DDD',
|
|
140
140
|
borderTopLeftRadius: 16,
|
|
141
|
-
borderTopRightRadius: 16,
|
|
141
|
+
borderTopRightRadius: 16, */
|
|
142
142
|
overflow: 'hidden',
|
|
143
143
|
},
|
|
144
144
|
outsideTouchable: {
|
package/src/layout/window.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect, useContext, useRef } from 'react';
|
|
2
2
|
import { Text, StyleSheet, View, TextInput, TouchableOpacity, Platform, KeyboardAvoidingView,
|
|
3
|
-
Keyboard, Animated } from 'react-native';
|
|
3
|
+
Keyboard, Animated, Linking, Button } from 'react-native';
|
|
4
4
|
import { Header } from '../components/header';
|
|
5
5
|
import { AppContext } from '../contexts/AppContext';
|
|
6
6
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
@@ -11,10 +11,11 @@ import { useWebSocketMessage } from '../hooks/stream';
|
|
|
11
11
|
import { ProductCard } from '../components/productCard'
|
|
12
12
|
import Markdown from 'react-native-markdown-display';
|
|
13
13
|
import { Feedback } from '../components/feedback';
|
|
14
|
+
import { ProgressCircle } from '../components/progressCircle';
|
|
14
15
|
|
|
15
16
|
export const ChatWindow = ({ panHandlers }) => {
|
|
16
17
|
const { handleSend, messages, input, setInput, ghostMessage, handleButtonClick,
|
|
17
|
-
onProductCardClick, onAddToCartClick, uiConfig
|
|
18
|
+
onProductCardClick, onAddToCartClick, uiConfig, ghostCard, typingIndicator
|
|
18
19
|
} = useContext(AppContext);
|
|
19
20
|
|
|
20
21
|
const scrollViewRef = useRef(null);
|
|
@@ -40,7 +41,11 @@ export const ChatWindow = ({ panHandlers }) => {
|
|
|
40
41
|
}, [ghostMessage]);
|
|
41
42
|
|
|
42
43
|
useWebSocketMessage()
|
|
43
|
-
|
|
44
|
+
|
|
45
|
+
const openLink = (url) => {
|
|
46
|
+
Linking.openURL(url);
|
|
47
|
+
};
|
|
48
|
+
|
|
44
49
|
return (
|
|
45
50
|
<View style={styles.container}>
|
|
46
51
|
<View {...panHandlers}>
|
|
@@ -76,12 +81,28 @@ export const ChatWindow = ({ panHandlers }) => {
|
|
|
76
81
|
</View>
|
|
77
82
|
)}
|
|
78
83
|
|
|
84
|
+
{msg.type == "middle" && (
|
|
85
|
+
<View style={[styles.middleMessageBubble, styles.middleMessage]}>
|
|
86
|
+
<Ionicons name="sparkles-outline" size={20} style={{marginRight: 10}}/>
|
|
87
|
+
<Markdown style={{ body: { color: msg.type === "user" ? "#ffffff" : "#161616",fontSize: 16, lineHeight: 22 }}}>
|
|
88
|
+
{msg.text}
|
|
89
|
+
</Markdown>
|
|
90
|
+
</View>
|
|
91
|
+
)}
|
|
92
|
+
|
|
79
93
|
{msg.products && msg.products.length > 0 &&
|
|
80
94
|
msg.products.map((prod, index) => (
|
|
81
95
|
<View key={index} style={styles.productCardWrapper}>
|
|
82
96
|
<ProductCard prod={prod} />
|
|
83
97
|
</View>
|
|
84
98
|
))}
|
|
99
|
+
|
|
100
|
+
{msg.resource && msg.resource.length > 0 && msg.resource_type == "video" &&
|
|
101
|
+
<TouchableOpacity style={styles.resourceButton} onPress={() => openLink(msg.resource)}>
|
|
102
|
+
<Text style={styles.resourceText}>Watch Video</Text>
|
|
103
|
+
<Ionicons name="link" size={20} color="white" style={styles.icon} />
|
|
104
|
+
</TouchableOpacity>
|
|
105
|
+
}
|
|
85
106
|
|
|
86
107
|
{msg.suggested_questions && Array.isArray(msg.questions) && msg.questions.map((question, index) => (
|
|
87
108
|
<TouchableOpacity key={index} style={styles.suggestedQuestionButton}
|
|
@@ -97,6 +118,18 @@ export const ChatWindow = ({ panHandlers }) => {
|
|
|
97
118
|
<Animated.View style={[styles.ghostBar, styles.ghostBarMedium, { opacity: fadeAnim }]} />
|
|
98
119
|
</View>
|
|
99
120
|
)}
|
|
121
|
+
|
|
122
|
+
{ghostCard && i === messages.length - 1 && (
|
|
123
|
+
<View style={styles.ghostCardContainer}>
|
|
124
|
+
<View style={styles.ghostSquare} />
|
|
125
|
+
<View style={styles.ghostBarsContainer}>
|
|
126
|
+
<Animated.View style={[styles.ghostBar, styles.ghostBarShort, { opacity: fadeAnim }]} />
|
|
127
|
+
<Animated.View style={[styles.ghostBar, styles.ghostBarLong, { opacity: fadeAnim }]} />
|
|
128
|
+
<Animated.View style={[styles.ghostBar, styles.ghostBarMedium, { opacity: fadeAnim }]} />
|
|
129
|
+
</View>
|
|
130
|
+
</View>
|
|
131
|
+
)}
|
|
132
|
+
|
|
100
133
|
</View>
|
|
101
134
|
))}
|
|
102
135
|
</KeyboardAwareScrollView>
|
|
@@ -111,7 +144,10 @@ export const ChatWindow = ({ panHandlers }) => {
|
|
|
111
144
|
onAddToCartClick={onAddToCartClick}
|
|
112
145
|
/>
|
|
113
146
|
}
|
|
114
|
-
|
|
147
|
+
{!typingIndicator ?
|
|
148
|
+
<ChatInput/> :
|
|
149
|
+
<ProgressCircle />
|
|
150
|
+
}
|
|
115
151
|
</KeyboardAvoidingView>
|
|
116
152
|
</View>
|
|
117
153
|
);
|
|
@@ -186,6 +222,55 @@ const styles = StyleSheet.create({
|
|
|
186
222
|
ghostBarLong: {
|
|
187
223
|
width: "100%",
|
|
188
224
|
},
|
|
225
|
+
ghostCardContainer: {
|
|
226
|
+
flexDirection: "row", // Arrange elements in a row
|
|
227
|
+
alignItems: "center", // Vertically align items
|
|
228
|
+
width: "100%",
|
|
229
|
+
backgroundColor: "#FFFFFF",
|
|
230
|
+
borderRadius: 10,
|
|
231
|
+
borderTopLeftRadius: 0,
|
|
232
|
+
padding: 14,
|
|
233
|
+
marginVertical: 5,
|
|
234
|
+
},
|
|
235
|
+
ghostSquare: {
|
|
236
|
+
width: "25%", // Takes up 25% of the container
|
|
237
|
+
aspectRatio: 1, // Makes it a square
|
|
238
|
+
backgroundColor: "#ebebeb", // Adjust color if needed
|
|
239
|
+
borderRadius: 8,
|
|
240
|
+
},
|
|
241
|
+
ghostBarsContainer: {
|
|
242
|
+
width: "75%", // Takes up 75% of the container
|
|
243
|
+
paddingLeft: 10, // Adds some spacing from the square
|
|
244
|
+
},
|
|
245
|
+
resourceButton: {
|
|
246
|
+
flexDirection: 'row',
|
|
247
|
+
alignItems: 'center',
|
|
248
|
+
backgroundColor: '#367CB6',
|
|
249
|
+
paddingVertical: 10,
|
|
250
|
+
paddingHorizontal: 15,
|
|
251
|
+
marginBottom: 5,
|
|
252
|
+
borderRadius: 5,
|
|
253
|
+
alignSelf: 'flex-start',
|
|
254
|
+
},
|
|
255
|
+
resourceText: {
|
|
256
|
+
color: 'white',
|
|
257
|
+
fontSize: 16,
|
|
258
|
+
marginRight: 8,
|
|
259
|
+
},
|
|
260
|
+
middleMessageBubble:{
|
|
261
|
+
padding: 6,
|
|
262
|
+
paddingHorizontal: 16,
|
|
263
|
+
borderRadius: 12,
|
|
264
|
+
marginBottom: 5,
|
|
265
|
+
flexDirection: 'row',
|
|
266
|
+
alignItems: 'center'
|
|
267
|
+
},
|
|
268
|
+
middleMessage:{
|
|
269
|
+
color: '#161616',
|
|
270
|
+
alignSelf: 'flex-start',
|
|
271
|
+
backgroundColor: '#e0f4fc', //'#e0f4fc',
|
|
272
|
+
width: '100%',
|
|
273
|
+
}
|
|
189
274
|
});
|
|
190
275
|
|
|
191
276
|
{/* <Testing
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// audioRecorder.js
|
|
2
|
+
import AudioRecorderPlayer from 'react-native-audio-recorder-player';
|
|
3
|
+
import { Platform, PermissionsAndroid } from 'react-native';
|
|
4
|
+
import { NativeModules } from 'react-native';
|
|
5
|
+
|
|
6
|
+
const audioRecorderPlayer = new AudioRecorderPlayer();
|
|
7
|
+
|
|
8
|
+
let silenceTimer = null;
|
|
9
|
+
let rmsValues = [];
|
|
10
|
+
const SILENCE_RMS_THRESHOLD = 0.02;
|
|
11
|
+
const SILENCE_LENGTH_MS = 2000;
|
|
12
|
+
|
|
13
|
+
export async function startRecording(handleStopRecording) {
|
|
14
|
+
|
|
15
|
+
audioRecorderPlayer = new AudioRecorderPlayer();
|
|
16
|
+
|
|
17
|
+
rmsValues = [];
|
|
18
|
+
audioRecorderPlayer.startRecorder();
|
|
19
|
+
|
|
20
|
+
audioRecorderPlayer.addRecordBackListener((e) => {
|
|
21
|
+
const rms = Math.sqrt(
|
|
22
|
+
e.currentMetering.reduce((sum, value) => sum + Math.pow(value / 100, 2), 0) / e.currentMetering.length
|
|
23
|
+
);
|
|
24
|
+
rmsValues.push(rms);
|
|
25
|
+
|
|
26
|
+
// Detect silence
|
|
27
|
+
if (rms > SILENCE_RMS_THRESHOLD) {
|
|
28
|
+
if (silenceTimer) {
|
|
29
|
+
clearTimeout(silenceTimer);
|
|
30
|
+
silenceTimer = null;
|
|
31
|
+
}
|
|
32
|
+
} else if (!silenceTimer) {
|
|
33
|
+
silenceTimer = setTimeout(() => {
|
|
34
|
+
handleStopRecording();
|
|
35
|
+
}, SILENCE_LENGTH_MS);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
console.log('Recording started...');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function stopRecording() {
|
|
42
|
+
if (!audioRecorderPlayer) {
|
|
43
|
+
console.error('AudioRecorderPlayer instance is null');
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (silenceTimer) {
|
|
47
|
+
clearTimeout(silenceTimer);
|
|
48
|
+
}
|
|
49
|
+
const result = await audioRecorderPlayer.stopRecorder();
|
|
50
|
+
console.log('Recording stopped:', result);
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function requestAudioPermission() {
|
|
55
|
+
if (Platform.OS === 'android') {
|
|
56
|
+
return await requestAndroidPermission();
|
|
57
|
+
} else if (Platform.OS === 'ios') {
|
|
58
|
+
return await requestIOSPermission();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ✅ Android: Request Microphone Permission
|
|
63
|
+
async function requestAndroidPermission() {
|
|
64
|
+
try {
|
|
65
|
+
const granted = await PermissionsAndroid.request(
|
|
66
|
+
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
|
|
67
|
+
{
|
|
68
|
+
title: 'Microphone Permission',
|
|
69
|
+
message: 'This app needs access to your microphone to record audio.',
|
|
70
|
+
buttonPositive: 'OK',
|
|
71
|
+
buttonNegative: 'Cancel',
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return granted === PermissionsAndroid.RESULTS.GRANTED;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('Error requesting microphone permission:', error);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ✅ iOS: Request Microphone Permission
|
|
83
|
+
async function requestIOSPermission() {
|
|
84
|
+
try {
|
|
85
|
+
const { AVAudioSession } = NativeModules;
|
|
86
|
+
if (AVAudioSession) {
|
|
87
|
+
await AVAudioSession.requestRecordPermission((granted) => {
|
|
88
|
+
console.log('iOS Microphone Permission:', granted);
|
|
89
|
+
return granted;
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
console.warn('AVAudioSession not available');
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('Error requesting microphone permission on iOS:', error);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// textToSpeech.js
|
|
2
|
+
import React,{ useState, useContext} from 'react';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import Sound from 'react-native-sound';
|
|
5
|
+
import { AppContext } from '../contexts/AppContext';
|
|
6
|
+
|
|
7
|
+
export const TextToSpeech = async (inputText) => {
|
|
8
|
+
const { data } = useContext(AppContext)
|
|
9
|
+
try {
|
|
10
|
+
const response = await axios.post(
|
|
11
|
+
'https://api.openai.com/v1/audio/speech',
|
|
12
|
+
{
|
|
13
|
+
model: 'tts-1',
|
|
14
|
+
voice: 'alloy',
|
|
15
|
+
input: inputText,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
headers: {
|
|
19
|
+
Authorization: `Bearer ${data.openai_key}`,
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
},
|
|
22
|
+
responseType: 'arraybuffer',
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const audioFile = `data:audio/mp3;base64,${Buffer.from(response.data).toString('base64')}`;
|
|
27
|
+
|
|
28
|
+
const sound = new Sound(audioFile, null, (error) => {
|
|
29
|
+
if (error) {
|
|
30
|
+
console.error('Error playing sound:', error);
|
|
31
|
+
} else {
|
|
32
|
+
sound.play();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Error generating TTS:', error);
|
|
37
|
+
}
|
|
38
|
+
};
|