Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/llm/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<queries>
<intent>
<action android:name="android.intent.action.VIEW"/>
Expand Down
17 changes: 16 additions & 1 deletion apps/llm/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,22 @@
"calendarPermission": "The app needs to access your calendar."
}
],
"expo-router"
"expo-router",
[
"react-native-audio-api",
{
"iosBackgroundMode": true,
"iosMicrophonePermission": "This app requires access to the microphone to record audio.",
"androidPermissions": [
"android.permission.MODIFY_AUDIO_SETTINGS",
"android.permission.FOREGROUND_SERVICE",
"android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK",
"android.permission.RECORD_AUDIO"
],
"androidForegroundService": true,
"androidFSTypes": ["mediaPlayback"]
}
]
],
"newArchEnabled": true,
"splash": {
Expand Down
58 changes: 22 additions & 36 deletions apps/llm/app/voice_chat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,10 @@ import MicIcon from '../../assets/icons/mic_icon.svg';
import StopIcon from '../../assets/icons/stop_icon.svg';
import ColorPalette from '../../colors';
import Messages from '../../components/Messages';
import LiveAudioStream from 'react-native-live-audio-stream';
import { AudioManager, AudioRecorder } from 'react-native-audio-api';
import DeviceInfo from 'react-native-device-info';
import { Buffer } from 'buffer';
import { useIsFocused } from '@react-navigation/native';
import { GeneratingContext } from '../../context';
const audioStreamOptions = {
sampleRate: 16000,
channels: 1,
bitsPerSample: 16,
audioSource: 1,
bufferSize: 16000,
};

const startStreamingAudio = (options: any, onChunk: (data: string) => void) => {
LiveAudioStream.init(options);
LiveAudioStream.on('data', onChunk);
LiveAudioStream.start();
};

const float32ArrayFromPCMBinaryBuffer = (b64EncodedBuffer: string) => {
const b64DecodedChunk = Buffer.from(b64EncodedBuffer, 'base64');
const int16Array = new Int16Array(b64DecodedChunk.buffer);

const float32Array = new Float32Array(int16Array.length);
for (let i = 0; i < int16Array.length; i++) {
float32Array[i] = Math.max(
-1,
Math.min(1, (int16Array[i] / audioStreamOptions.bufferSize) * 8)
);
}
return float32Array;
};

export default function VoiceChatScreenWrapper() {
const isFocused = useIsFocused();
Expand All @@ -63,6 +35,13 @@ export default function VoiceChatScreenWrapper() {

function VoiceChatScreen() {
const [isRecording, setIsRecording] = useState(false);
const [recorder] = useState(
() =>
new AudioRecorder({
sampleRate: 16000,
bufferLengthInSamples: 1600,
})
);
const messageRecorded = useRef<boolean>(false);
const { setGlobalGenerating } = useContext(GeneratingContext);

Expand All @@ -75,20 +54,27 @@ function VoiceChatScreen() {
setGlobalGenerating(llm.isGenerating || speechToText.isGenerating);
}, [llm.isGenerating, speechToText.isGenerating, setGlobalGenerating]);

const onChunk = (data: string) => {
const float32Chunk = float32ArrayFromPCMBinaryBuffer(data);
speechToText.streamInsert(Array.from(float32Chunk));
};
useEffect(() => {
AudioManager.setAudioSessionOptions({
iosCategory: 'playAndRecord',
iosMode: 'spokenAudio',
iosOptions: ['allowBluetooth', 'defaultToSpeaker'],
});
AudioManager.requestRecordingPermissions();
}, []);

const handleRecordPress = async () => {
if (isRecording) {
setIsRecording(false);
LiveAudioStream.stop();
recorder.stop();
messageRecorded.current = true;
speechToText.streamStop();
await speechToText.streamStop();
} else {
setIsRecording(true);
startStreamingAudio(audioStreamOptions, onChunk);
recorder.onAudioReady(async ({ buffer }) => {
await speechToText.streamInsert(buffer.getChannelData(0));
});
recorder.start();
const transcription = await speechToText.stream();
await llm.sendMessage(transcription);
}
Expand Down
22 changes: 8 additions & 14 deletions apps/llm/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1403,7 +1403,7 @@ PODS:
- React-jsiexecutor
- React-RCTFBReactNativeSpec
- ReactCommon/turbomodule/core
- react-native-executorch (0.5.2):
- react-native-executorch (0.5.0):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -1826,7 +1826,7 @@ PODS:
- React-logger (= 0.79.2)
- React-perflogger (= 0.79.2)
- React-utils (= 0.79.2)
- RNAudioAPI (0.5.7):
- RNAudioAPI (0.8.2):
- DoubleConversion
- glog
- hermes-engine
Expand All @@ -1849,9 +1849,9 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNAudioAPI/audioapi (= 0.5.7)
- RNAudioAPI/audioapi (= 0.8.2)
- Yoga
- RNAudioAPI/audioapi (0.5.7):
- RNAudioAPI/audioapi (0.8.2):
- DoubleConversion
- glog
- hermes-engine
Expand All @@ -1874,9 +1874,9 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNAudioAPI/audioapi/ios (= 0.5.7)
- RNAudioAPI/audioapi/ios (= 0.8.2)
- Yoga
- RNAudioAPI/audioapi/ios (0.5.7):
- RNAudioAPI/audioapi/ios (0.8.2):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -1926,8 +1926,6 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNLiveAudioStream (1.1.1):
- React
- RNReanimated (3.17.5):
- DoubleConversion
- glog
Expand Down Expand Up @@ -2246,7 +2244,6 @@ DEPENDENCIES:
- RNAudioAPI (from `../../../node_modules/react-native-audio-api`)
- RNDeviceInfo (from `../../../node_modules/react-native-device-info`)
- RNGestureHandler (from `../../../node_modules/react-native-gesture-handler`)
- RNLiveAudioStream (from `../../../node_modules/react-native-live-audio-stream`)
- RNReanimated (from `../../../node_modules/react-native-reanimated`)
- RNScreens (from `../../../node_modules/react-native-screens`)
- RNSVG (from `../../../node_modules/react-native-svg`)
Expand Down Expand Up @@ -2430,8 +2427,6 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native-device-info"
RNGestureHandler:
:path: "../../../node_modules/react-native-gesture-handler"
RNLiveAudioStream:
:path: "../../../node_modules/react-native-live-audio-stream"
RNReanimated:
:path: "../../../node_modules/react-native-reanimated"
RNScreens:
Expand Down Expand Up @@ -2525,15 +2520,14 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 04d5eb15eb46be6720e17a4a7fa92940a776e584
ReactCodegen: 7ea266ccd94436294f516247db7402b57b1214af
ReactCommon: 76d2dc87136d0a667678668b86f0fca0c16fdeb0
RNAudioAPI: 2e3fd4bf75aa5717791babb30126707504996f09
RNAudioAPI: 3e398c4e9d44bb6b0c0b00e902057613224fc024
RNDeviceInfo: d863506092aef7e7af3a1c350c913d867d795047
RNGestureHandler: 7d0931a61d7ba0259f32db0ba7d0963c3ed15d2b
RNLiveAudioStream: 93ac2bb6065be9018d0b00157b220f11cebc1513
RNReanimated: afd6a269a47d6f13ba295c46c6c0e14e3cbd0d8a
RNScreens: 482e9707f9826230810c92e765751af53826d509
RNSVG: 794f269526df9ddc1f79b3d1a202b619df0368e3
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
sqlite3: 83105acd294c9137c026e2da1931c30b4588ab81
sqlite3: 1d85290c3321153511f6e900ede7a1608718bbd5
Yoga: c758bfb934100bb4bf9cbaccb52557cee35e8bdf

PODFILE CHECKSUM: bba19a069e673f2259009e9d2caab44374fdebcf
Expand Down
59 changes: 56 additions & 3 deletions apps/llm/ios/llm.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,11 @@
B79E360E00239D910BF9B38D /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = llm/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
E8C01EF33FCE4105BBBC9DF6 /* Aeonik-Medium.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Aeonik-Medium.otf"; path = "../assets/fonts/Aeonik-Medium.otf"; sourceTree = "<group>"; };
EA4529BE680FEB0AB7539557 /* Pods-llm.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-llm.release.xcconfig"; path = "Target Support Files/Pods-llm/Pods-llm.release.xcconfig"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = llm/AppDelegate.swift; sourceTree = "<group>"; };
F11748442D0722820044C1D9 /* llm-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "llm-Bridging-Header.h"; path = "llm/llm-Bridging-Header.h"; sourceTree = "<group>"; };
F5CE0775ADE5923FA417B603 /* libPods-llm.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-llm.a"; sourceTree = BUILT_PRODUCTS_DIR; };
F866B7979FB94C8797EE2E3D /* Aeonik-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Aeonik-Regular.otf"; path = "../assets/fonts/Aeonik-Regular.otf"; sourceTree = "<group>"; };
FCA4A9AE0011869427989B32 /* libPods-llm.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-llm.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -96,6 +95,15 @@
name = Frameworks;
sourceTree = "<group>";
};
3014A6CAF64EC97E4003A2A3 /* Pods */ = {
isa = PBXGroup;
children = (
4F489A14802F01369BFDDEFD /* Pods-llm.debug.xcconfig */,
63C842393C3838DA2ECEFC7C /* Pods-llm.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -292,7 +300,52 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
E0CDBD4D0993974173A0E9FD /* [CP] Copy Pods Resources */ = {
281D8603161F8B331E2BA335 /* [Expo] Configure project */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "[Expo] Configure project";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-llm/expo-configure-project.sh\"\n";
};
62055444ECB4CA2743E68CDC /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-llm/Pods-llm-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/RNAudioAPI/libavcodec.framework/libavcodec",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/RNAudioAPI/libavformat.framework/libavformat",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/RNAudioAPI/libavutil.framework/libavutil",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/RNAudioAPI/libswresample.framework/libswresample",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libavcodec.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libavformat.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libavutil.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libswresample.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-llm/Pods-llm-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
Expand Down
31 changes: 16 additions & 15 deletions apps/llm/metro.config.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
const { getDefaultConfig } = require('expo/metro-config');
const {
wrapWithAudioAPIMetroConfig,
} = require('react-native-audio-api/metro-config');

module.exports = (() => {
const config = getDefaultConfig(__dirname);
const config = getDefaultConfig(__dirname);

const { transformer, resolver } = config;
const { transformer, resolver } = config;

config.transformer = {
...transformer,
babelTransformerPath: require.resolve('react-native-svg-transformer/expo'),
};
config.resolver = {
...resolver,
assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...resolver.sourceExts, 'svg'],
};
config.transformer = {
...transformer,
babelTransformerPath: require.resolve('react-native-svg-transformer/expo'),
};
config.resolver = {
...resolver,
assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...resolver.sourceExts, 'svg'],
};

config.resolver.assetExts.push('pte');
config.resolver.assetExts.push('pte');

return config;
})();
module.exports = wrapWithAudioAPIMetroConfig(config);
3 changes: 1 addition & 2 deletions apps/llm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,10 @@
"metro-config": "^0.81.0",
"react": "19.0.0",
"react-native": "0.79.2",
"react-native-audio-api": "0.5.7",
"react-native-audio-api": "^0.8.2",
"react-native-device-info": "^14.0.4",
"react-native-executorch": "workspace:*",
"react-native-gesture-handler": "~2.24.0",
"react-native-live-audio-stream": "^1.1.1",
"react-native-loading-spinner-overlay": "^3.0.1",
"react-native-markdown-display": "^7.0.2",
"react-native-reanimated": "~3.17.4",
Expand Down
4 changes: 2 additions & 2 deletions apps/speech-to-text/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1395,7 +1395,7 @@ PODS:
- React-jsiexecutor
- React-RCTFBReactNativeSpec
- ReactCommon/turbomodule/core
- react-native-executorch (0.4.2):
- react-native-executorch (0.5.0):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -2382,7 +2382,7 @@ SPEC CHECKSUMS:
React-logger: 8edfcedc100544791cd82692ca5a574240a16219
React-Mapbuffer: c3f4b608e4a59dd2f6a416ef4d47a14400194468
React-microtasksnativemodule: 054f34e9b82f02bd40f09cebd4083828b5b2beb6
react-native-executorch: c18d209e226f0530a9ee88f1d60ce5837d4800ee
react-native-executorch: 3c871f7ed2e2b0ff92519ce38f06f0904784dbdb
react-native-safe-area-context: 562163222d999b79a51577eda2ea8ad2c32b4d06
React-NativeModulesApple: 2c4377e139522c3d73f5df582e4f051a838ff25e
React-oscompat: ef5df1c734f19b8003e149317d041b8ce1f7d29c
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,6 @@
"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/react-native-image-picker/RNImagePickerPrivacyInfo.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
Expand All @@ -290,7 +289,6 @@
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImagePickerPrivacyInfo.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
Expand All @@ -305,12 +303,10 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-speechtotext/Pods-speechtotext-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/react-native-executorch/ExecutorchLib.framework/ExecutorchLib",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ExecutorchLib.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
Expand Down
Loading
Loading