-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
22 changed files
with
9,229 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files | ||
|
||
# dependencies | ||
node_modules/ | ||
|
||
# Expo | ||
.expo/ | ||
dist/ | ||
web-build/ | ||
|
||
# Native | ||
*.orig.* | ||
*.jks | ||
*.p8 | ||
*.p12 | ||
*.key | ||
*.mobileprovision | ||
.env | ||
|
||
# Metro | ||
.metro-health-check* | ||
|
||
# debug | ||
npm-debug.* | ||
yarn-debug.* | ||
yarn-error.* | ||
|
||
# macOS | ||
.DS_Store | ||
*.pem | ||
|
||
# local env files | ||
.env*.local | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
|
||
ios | ||
android |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node-linker=hoisted |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
import React, { useEffect, useState, useRef } from 'react'; | ||
import { | ||
View, | ||
Text, | ||
Button, | ||
StyleSheet, | ||
ScrollView, | ||
SafeAreaView, | ||
} from 'react-native'; | ||
import { HumeClient, type Hume } from 'hume' | ||
|
||
import * as NativeAudio from './modules/audio'; | ||
|
||
interface ChatEntry { | ||
role: 'user' | 'assistant'; | ||
timestamp: string; | ||
content: string; | ||
} | ||
|
||
const hume = new HumeClient({ | ||
apiKey: process.env.EXPO_PUBLIC_HUME_API_KEY || '' | ||
}) | ||
const App = () => { | ||
const [isConnected, setIsConnected] = useState(false); | ||
const [isMuted, setIsMuted] = useState(false); | ||
const [chatEntries, setChatEntries] = useState<ChatEntry[]>([ | ||
{ role: 'assistant', timestamp: new Date().toString(), content: 'Hello! How can I help you today?' }, | ||
{ role: 'user', timestamp: new Date().toString(), content: 'I am beyond help' }, | ||
|
||
]); | ||
const [playbackQueue, setPlaybackQueue] = useState<any[]>([]); | ||
|
||
const chatSocketRef = useRef<Hume.empathicVoice.chat.ChatSocket | null>(null); | ||
|
||
useEffect(() => { | ||
if (isConnected) { | ||
NativeAudio.getPermissions().then(() => { | ||
NativeAudio.startRecording(); | ||
}).catch((error) => { | ||
console.error('Failed to get permissions:', error); | ||
}) | ||
const chatSocket = hume.empathicVoice.chat.connect({ | ||
configId: process.env.EXPO_PUBLIC_HUME_CONFIG_ID, | ||
}) | ||
chatSocket.on('message', handleIncomingMessage); | ||
|
||
chatSocket.on('error', (error) => { | ||
console.error("WebSocket Error:", error); | ||
}); | ||
|
||
chatSocket.on('close', () => { | ||
console.log("WebSocket Connection Closed"); | ||
setIsConnected(false); | ||
}); | ||
|
||
chatSocketRef.current = chatSocket; | ||
|
||
NativeAudio.onAudioInput(({base64EncodedAudio}: NativeAudio.AudioEventPayload) => { | ||
chatSocket.sendAudioInput({data: base64EncodedAudio}); | ||
}) | ||
} else { | ||
NativeAudio.stopRecording(); | ||
} | ||
return () => { | ||
NativeAudio.stopRecording(); | ||
chatSocketRef.current?.close(); | ||
} | ||
}, [isConnected]); | ||
|
||
const handleIncomingMessage = (message: any) => { | ||
if (message.type === 'audio_output') { | ||
const audioData = message.data; | ||
const decodedAudio = atob(audioData); | ||
playAudio(decodedAudio); | ||
} else if (message.type === 'chat_message') { | ||
const chatEntry: ChatEntry = { | ||
role: message.role === 'assistant' ? 'assistant' : 'user', | ||
timestamp: new Date().toString(), | ||
content: message.content, | ||
}; | ||
setChatEntries((prev) => [...prev, chatEntry]); | ||
} | ||
}; | ||
|
||
const connectToWebSocket = () => { | ||
setIsConnected(true); | ||
}; | ||
|
||
const disconnectFromWebSocket = () => { | ||
if (chatSocketRef.current) { | ||
chatSocketRef.current.close(); | ||
} | ||
setIsConnected(false); | ||
}; | ||
|
||
const muteInput = () => { | ||
setIsMuted(true); | ||
NativeAudio.stopRecording(); | ||
}; | ||
|
||
const unmuteInput = () => { | ||
setIsMuted(false); | ||
NativeAudio.startRecording(); | ||
}; | ||
|
||
const playAudio = (audioData: string) => { | ||
if (playbackQueue.length > 0) { | ||
setPlaybackQueue((prev) => [...prev, audioData]); | ||
} else { | ||
NativeAudio.playAudio(audioData); | ||
} | ||
}; | ||
|
||
return ( | ||
<View style={styles.appBackground}> | ||
<SafeAreaView style={styles.container}> | ||
<View style={styles.header}> | ||
<Text style={styles.headerText}>You are {isConnected ? 'connected' : 'disconnected'}</Text> | ||
</View> | ||
<ScrollView style={styles.chatDisplay}> | ||
{chatEntries.map((entry, index) => ( | ||
<View | ||
key={index} | ||
style={[ | ||
styles.chatEntry, | ||
entry.role === 'user' ? styles.userChatEntry : styles.assistantChatEntry, | ||
]} | ||
> | ||
<Text style={styles.chatText}>{entry.content}</Text> | ||
</View> | ||
))} | ||
</ScrollView> | ||
<View style={styles.buttonContainer}> | ||
<Button | ||
title={isConnected ? 'Disconnect' : 'Connect'} | ||
onPress={isConnected ? disconnectFromWebSocket : connectToWebSocket} | ||
/> | ||
<Button title={isMuted ? 'Unmute' : 'Mute'} onPress={isMuted ? unmuteInput : muteInput} /> | ||
</View> | ||
</SafeAreaView> | ||
</View> | ||
); | ||
}; | ||
|
||
const styles = StyleSheet.create({ | ||
appBackground: { | ||
flex: 1, | ||
backgroundColor: 'rgb(255, 244, 232)', | ||
alignItems: 'center', | ||
}, | ||
container: { | ||
flex: 1, | ||
justifyContent: 'center', | ||
padding: 16, | ||
maxWidth: 600, | ||
width: '100%' | ||
}, | ||
header: { | ||
marginBottom: 16, | ||
alignItems: 'center', | ||
}, | ||
headerText: { | ||
fontSize: 18, | ||
fontWeight: 'bold', | ||
}, | ||
chatDisplay: { | ||
flex: 1, | ||
width: '100%', | ||
marginBottom: 16, | ||
}, | ||
chatEntry: { | ||
padding: 10, | ||
marginVertical: 5, | ||
borderRadius: 15, | ||
maxWidth: '75%', | ||
shadowColor: '#000', | ||
shadowOffset: { | ||
width: 0, | ||
height: 2, | ||
}, | ||
shadowOpacity: 0.1, | ||
shadowRadius: 2, | ||
elevation: 3, | ||
}, | ||
userChatEntry: { | ||
backgroundColor: 'rgb(209, 226, 243)', | ||
alignSelf: 'flex-end', | ||
marginRight: 10, | ||
}, | ||
assistantChatEntry: { | ||
backgroundColor: '#fff', | ||
alignSelf: 'flex-start', | ||
marginLeft: 10, | ||
}, | ||
chatText: { | ||
fontSize: 16, | ||
}, | ||
buttonContainer: { | ||
flexDirection: 'row', | ||
justifyContent: 'space-between', | ||
width: '100%', | ||
paddingHorizontal: 16, | ||
paddingVertical: 8, | ||
}, | ||
}); | ||
|
||
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
"expo": { | ||
"name": "EVIExample", | ||
"slug": "EVIExample", | ||
"version": "1.0.0", | ||
"orientation": "portrait", | ||
"icon": "./assets/icon.png", | ||
"userInterfaceStyle": "light", | ||
"splash": { | ||
"image": "./assets/splash.png", | ||
"resizeMode": "contain", | ||
"backgroundColor": "#ffffff" | ||
}, | ||
"ios": { | ||
"supportsTablet": true, | ||
"bundleIdentifier": "com.example.EVIExample", | ||
"infoPlist": { | ||
"NSMicrophoneUsageDescription": "This app uses the microphone to allow the user to talk to the EVI conversational AI interface" | ||
} | ||
}, | ||
"android": { | ||
"adaptiveIcon": { | ||
"foregroundImage": "./assets/adaptive-icon.png", | ||
"backgroundColor": "#ffffff" | ||
}, | ||
"package": "com.example.EVIExample" | ||
}, | ||
"web": { | ||
"favicon": "./assets/favicon.png" | ||
} | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module.exports = function(api) { | ||
api.cache(true); | ||
return { | ||
presets: ['babel-preset-expo'], | ||
}; | ||
}; |
43 changes: 43 additions & 0 deletions
43
evi-react-native-example/EVIExample/modules/audio/android/build.gradle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
apply plugin: 'com.android.library' | ||
|
||
group = 'expo.modules.audio' | ||
version = '0.6.0' | ||
|
||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") | ||
apply from: expoModulesCorePlugin | ||
applyKotlinExpoModulesCorePlugin() | ||
useCoreDependencies() | ||
useExpoPublishing() | ||
|
||
// If you want to use the managed Android SDK versions from expo-modules-core, set this to true. | ||
// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code. | ||
// Most of the time, you may like to manage the Android SDK versions yourself. | ||
def useManagedAndroidSdkVersions = false | ||
if (useManagedAndroidSdkVersions) { | ||
useDefaultAndroidSdkVersions() | ||
} else { | ||
buildscript { | ||
// Simple helper that allows the root project to override versions declared by this library. | ||
ext.safeExtGet = { prop, fallback -> | ||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback | ||
} | ||
} | ||
project.android { | ||
compileSdkVersion safeExtGet("compileSdkVersion", 34) | ||
defaultConfig { | ||
minSdkVersion safeExtGet("minSdkVersion", 21) | ||
targetSdkVersion safeExtGet("targetSdkVersion", 34) | ||
} | ||
} | ||
} | ||
|
||
android { | ||
namespace "expo.modules.audio" | ||
defaultConfig { | ||
versionCode 1 | ||
versionName "0.6.0" | ||
} | ||
lintOptions { | ||
abortOnError false | ||
} | ||
} |
2 changes: 2 additions & 0 deletions
2
evi-react-native-example/EVIExample/modules/audio/android/src/main/AndroidManifest.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
<manifest> | ||
</manifest> |
47 changes: 47 additions & 0 deletions
47
...-example/EVIExample/modules/audio/android/src/main/java/expo/modules/audio/AudioModule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package expo.modules.audio | ||
|
||
import expo.modules.kotlin.modules.Module | ||
import expo.modules.kotlin.modules.ModuleDefinition | ||
|
||
class AudioModule : Module() { | ||
// Each module class must implement the definition function. The definition consists of components | ||
// that describes the module's functionality and behavior. | ||
// See https://docs.expo.dev/modules/module-api for more details about available components. | ||
override fun definition() = ModuleDefinition { | ||
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument. | ||
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity. | ||
// The module will be accessible from `requireNativeModule('Audio')` in JavaScript. | ||
Name("Audio") | ||
|
||
// Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary. | ||
Constants( | ||
"PI" to Math.PI | ||
) | ||
|
||
// Defines event names that the module can send to JavaScript. | ||
Events("onChange") | ||
|
||
// Defines a JavaScript synchronous function that runs the native code on the JavaScript thread. | ||
Function("hello") { | ||
"Hello world! 👋" | ||
} | ||
|
||
// Defines a JavaScript function that always returns a Promise and whose native code | ||
// is by default dispatched on the different thread than the JavaScript runtime runs on. | ||
AsyncFunction("setValueAsync") { value: String -> | ||
// Send an event to JavaScript. | ||
sendEvent("onChange", mapOf( | ||
"value" to value | ||
)) | ||
} | ||
|
||
// Enables the module to be used as a native view. Definition components that are accepted as part of | ||
// the view definition: Prop, Events. | ||
View(AudioView::class) { | ||
// Defines a setter for the `name` prop. | ||
Prop("name") { view: AudioView, prop: String -> | ||
println(prop) | ||
} | ||
} | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
evi-react-native-example/EVIExample/modules/audio/expo-module.config.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"platforms": ["ios", "tvos", "android", "web"], | ||
"ios": { | ||
"modules": ["AudioModule"] | ||
}, | ||
"android": { | ||
"modules": ["expo.modules.audio.AudioModule"] | ||
} | ||
} |
Oops, something went wrong.