Creating a RN example for simple-chatbot

This commit is contained in:
Filipi Fuchter
2025-05-05 18:17:39 -03:00
parent b323a7bd88
commit 40154824e8
26 changed files with 12366 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
import { View, Image, StyleSheet, LayoutChangeEvent, ImageStyle, ViewStyle } from 'react-native';
import React, { useMemo, useState } from 'react';
import { Icons } from '../theme/Assets';
import Colors from '../theme/Colors';
import { useVoiceClient } from '../context/VoiceClientContext';
import { VoiceClientVideoView } from '@pipecat-ai/react-native-daily-transport';
interface CameraButtonViewProps {
style?: ViewStyle; // Optional additional styles for the button container
}
const CameraButtonView: React.FC<CameraButtonViewProps> = ({ style }) => {
const { videoTrack, isCamEnabled } = useVoiceClient();
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const onLayout = (event: LayoutChangeEvent) => {
const { width, height } = event.nativeEvent.layout;
setDimensions({ width, height });
};
const mediaComponent = useMemo(() => {
return (
<VoiceClientVideoView
videoTrack={videoTrack || null}
audioTrack={null}
mirror={true}
zOrder={1}
style={styles.media}
objectFit="cover"
/>
);
}, [videoTrack]);
const { width } = dimensions;
const circleSize = width * 0.9;
const innerCircleSize = width * 0.82;
return (
<View style={[styles.container, style]} onLayout={onLayout}>
<View
style={[
styles.outerCircle,
{ width: circleSize, height: circleSize, borderRadius: circleSize / 2 },
]}
>
{isCamEnabled ? (
<View style={[styles.videoView, { borderRadius: circleSize / 2 }]}>
{mediaComponent}
</View>
) : (
<>
<View
style={[
styles.innerCircle,
{
width: innerCircleSize,
height: innerCircleSize,
borderRadius: innerCircleSize / 2,
},
]}
/>
<Image
source={Icons.vision}
style={[
styles.image,
{
width: width * 0.3,
height: width * 0.3,
tintColor: 'green',
},
]}
resizeMode="contain"
/>
</>
)}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
} as ViewStyle,
outerCircle: {
borderWidth: 1,
borderColor: Colors.buttonsBorder,
justifyContent: 'center',
alignItems: 'center',
} as ViewStyle,
innerCircle: {
backgroundColor: Colors.disabledVision,
position: 'absolute',
} as ViewStyle,
videoView: {
aspectRatio: 1,
width: '100%',
height: '100%',
overflow: 'hidden',
} as ViewStyle,
image: {} as ImageStyle,
media: {
width: '100%',
height: '100%',
position: 'absolute',
},
});
export default CameraButtonView;

View File

@@ -0,0 +1,94 @@
import React, { useState, useMemo } from 'react';
import { View, StyleSheet, LayoutChangeEvent, ViewStyle } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import Colors from '../theme/Colors';
import { useVoiceClient } from '../context/VoiceClientContext';
interface MicrophoneViewProps {
style?: ViewStyle; // Optional additional styles for the button container
}
const MicrophoneView: React.FC<MicrophoneViewProps> = ({ style }) => {
const { isMicEnabled, localAudioLevel: audioLevel } = useVoiceClient();
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const onLayout = (event: LayoutChangeEvent) => {
const { width, height } = event.nativeEvent.layout;
setDimensions({ width, height });
};
const { width } = dimensions;
const circleSize = useMemo(() => width * 0.9, [width]);
const innerCircleSize = useMemo(() => width * 0.82, [width]);
const audioCircleSize = useMemo(() => audioLevel * width * 0.95, [audioLevel, width]);
return (
<View style={[styles.container, style]} onLayout={onLayout}>
<View
style={[
styles.outerCircle,
{ width: circleSize, height: circleSize, borderRadius: circleSize / 2 },
]}
>
<View
style={[
styles.innerCircle,
{
backgroundColor: !isMicEnabled ? Colors.disabledMic : Colors.backgroundCircle,
width: innerCircleSize,
height: innerCircleSize,
borderRadius: innerCircleSize / 2,
},
]}
/>
{isMicEnabled && (
<View
style={[
styles.audioCircle,
{
width: audioCircleSize,
height: audioCircleSize,
borderRadius: audioCircleSize / 2,
},
]}
/>
)}
<MaterialIcons
name={!isMicEnabled ? "mic-off" : "mic"}
size={width * 0.2}
color="white"
style={styles.micIcon}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
} as ViewStyle,
outerCircle: {
borderWidth: 1,
borderColor: Colors.buttonsBorder,
justifyContent: 'center',
alignItems: 'center',
} as ViewStyle,
innerCircle: {
position: 'absolute',
} as ViewStyle,
audioCircle: {
position: 'absolute',
backgroundColor: Colors.micVolume,
opacity: 0.5,
} as ViewStyle,
micIcon: {
position: 'absolute',
},
});
export default MicrophoneView;

View File

@@ -0,0 +1,128 @@
import React, { useEffect, useState } from 'react';
import { LayoutChangeEvent, StyleSheet, Text, View, ViewStyle } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import Colors from '../theme/Colors';
import { useVoiceClient } from '../context/VoiceClientContext';
const dotCount = 5;
const WaveformView: React.FC = () => {
const [audioLevels, setAudioLevels] = useState(Array(dotCount).fill(0));
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const { currentState: voiceClientStatus, botReady: isBotReady, remoteAudioLevel: audioLevel } = useVoiceClient();
const onLayout = (event: LayoutChangeEvent) => {
const { width, height } = event.nativeEvent.layout;
setDimensions({ width, height });
};
useEffect(() => {
setAudioLevels((prevLevels) => [...prevLevels.slice(1), audioLevel]);
}, [audioLevel]);
const { width, height } = dimensions;
const circleSize = width * 0.9;
const innerCircleSize = width * 0.82;
const barWidth = (width * 0.5) / dotCount;
return (
<View style={styles.container} onLayout={onLayout}>
<View style={[styles.outerCircle, { width: circleSize, height: circleSize, borderRadius: circleSize / 2 }]}>
<View
style={[
styles.innerCircle,
{
backgroundColor: isBotReady ? Colors.backgroundCircle : Colors.backgroundCircleNotConnected,
width: innerCircleSize,
height: innerCircleSize,
borderRadius: innerCircleSize / 2,
},
]}
>
{isBotReady ? (
audioLevel > 0 ? (
<View style={[styles.waveformContainer, { width: width * 0.5, height: width * 0.5 }]}>
{audioLevels.map((level, index) => (
<View
key={index}
style={[
styles.waveformBar,
{
width: barWidth - 10, // Subtract some margin
height: level * height,
},
]}
/>
))}
</View>
) : (
<View style={[styles.dotContainer, { width: width * 0.5, height: height * 0.5 }]}>
{Array(dotCount)
.fill(0)
.map((_, index) => (
<View key={index} style={[styles.dot, { width: height * 0.1, height: height * 0.1 }]} />
))}
</View>
)
) : (
<View style={styles.notReadyContainer}>
<MaterialIcons name="hourglass-empty" size={32} color="white" />
<Text style={styles.voiceClientStatusText}>{voiceClientStatus}</Text>
</View>
)}
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
width: "100%",
} as ViewStyle,
outerCircle: {
borderWidth: 1,
borderColor: 'gray',
justifyContent: 'center',
alignItems: 'center',
} as ViewStyle,
innerCircle: {
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
} as ViewStyle,
waveformContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
} as ViewStyle,
waveformBar: {
backgroundColor: 'white',
maxHeight: '100%',
borderRadius: 12,
} as ViewStyle,
dotContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
} as ViewStyle,
dot: {
backgroundColor: 'white',
borderRadius: 50,
} as ViewStyle,
notReadyContainer: {
justifyContent: 'center',
alignItems: 'center',
} as ViewStyle,
voiceClientStatusText: {
color: 'white',
marginTop: 10,
fontSize: 16,
fontWeight: 'bold',
} as ViewStyle,
});
export default WaveformView;