Skip to content

Commit 293a03b

Browse files
author
Mateusz Kopciński
committed
modified llm example
1 parent e7d352a commit 293a03b

File tree

9 files changed

+1154
-1409
lines changed

9 files changed

+1154
-1409
lines changed

examples/llm/App.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import {
1414
} from 'react-native';
1515
import LLMScreen from './screens/LLMScreen';
1616
import LLMToolCallingScreen from './screens/LLMToolCallingScreen';
17+
import VoiceChatScreen from './screens/VocieChatScreen';
1718

1819
enum Mode {
1920
LLM,
21+
LLM_VOICE_CHAT,
2022
LLM_TOOL_CALLING,
2123
}
2224

@@ -39,6 +41,9 @@ export default function App() {
3941
case Mode.LLM:
4042
return <LLMScreen setIsGenerating={setIsGenerating} />;
4143

44+
case Mode.LLM_VOICE_CHAT:
45+
return <VoiceChatScreen setIsGenerating={setIsGenerating} />;
46+
4247
case Mode.LLM_TOOL_CALLING:
4348
return <LLMToolCallingScreen setIsGenerating={setIsGenerating} />;
4449

@@ -61,7 +66,7 @@ export default function App() {
6166
{!isGenerating ? (
6267
<View style={styles.wheelPickerContainer}>
6368
<ScrollPicker
64-
dataSource={['Chat with LLM', 'Tool calling']}
69+
dataSource={['Chat with LLM', 'Talk to LLM', 'Tool calling']}
6570
onValueChange={(_, selectedIndex) => {
6671
handleModeChange(selectedIndex);
6772
}}

examples/llm/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,7 +1401,7 @@ PODS:
14011401
- React-jsiexecutor
14021402
- React-RCTFBReactNativeSpec
14031403
- ReactCommon/turbomodule/core
1404-
- react-native-executorch (0.3.3):
1404+
- react-native-executorch (0.3.1-stt-12):
14051405
- DoubleConversion
14061406
- glog
14071407
- hermes-engine
@@ -2395,7 +2395,7 @@ SPEC CHECKSUMS:
23952395
React-logger: 8edfcedc100544791cd82692ca5a574240a16219
23962396
React-Mapbuffer: c3f4b608e4a59dd2f6a416ef4d47a14400194468
23972397
React-microtasksnativemodule: 054f34e9b82f02bd40f09cebd4083828b5b2beb6
2398-
react-native-executorch: d0c3dffa0a4a4111ea9c7b97f3fbf088a48d3b2a
2398+
react-native-executorch: 8835fcfdfc71b1d42d30525ee047b2811c359cb8
23992399
react-native-safe-area-context: 562163222d999b79a51577eda2ea8ad2c32b4d06
24002400
React-NativeModulesApple: 2c4377e139522c3d73f5df582e4f051a838ff25e
24012401
React-oscompat: ef5df1c734f19b8003e149317d041b8ce1f7d29c

examples/llm/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"react": "19.0.0",
2121
"react-native": "^0.79.2",
2222
"react-native-audio-api": "0.5.7",
23-
"react-native-executorch": "^0.3.1",
23+
"react-native-executorch": "^0.3.2",
2424
"react-native-live-audio-stream": "^1.1.1",
2525
"react-native-loading-spinner-overlay": "^3.0.1",
2626
"react-native-markdown-display": "^7.0.2",

examples/llm/screens/LLMScreen.tsx

Lines changed: 7 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -13,98 +13,31 @@ import {
1313
import SWMIcon from '../assets/icons/swm_icon.svg';
1414
import Spinner from 'react-native-loading-spinner-overlay';
1515
import {
16-
STREAMING_ACTION,
17-
useSpeechToText,
1816
useLLM,
1917
QWEN3_0_6B_QUANTIZED,
2018
QWEN3_TOKENIZER,
2119
QWEN3_TOKENIZER_CONFIG,
2220
} from 'react-native-executorch';
2321
import PauseIcon from '../assets/icons/pause_icon.svg';
24-
import MicIcon from '../assets/icons/mic_icon.svg';
2522
import SendIcon from '../assets/icons/send_icon.svg';
26-
import StopIcon from '../assets/icons/stop_icon.svg';
2723
import ColorPalette from '../colors';
2824
import Messages from '../components/Messages';
29-
import LiveAudioStream from 'react-native-live-audio-stream';
30-
import { Buffer } from 'buffer';
31-
32-
const audioStreamOptions = {
33-
sampleRate: 16000,
34-
channels: 1,
35-
bitsPerSample: 16,
36-
audioSource: 1,
37-
bufferSize: 16000,
38-
};
39-
40-
const startStreamingAudio = (options: any, onChunk: (data: string) => void) => {
41-
LiveAudioStream.init(options);
42-
LiveAudioStream.on('data', onChunk);
43-
LiveAudioStream.start();
44-
};
45-
46-
const float32ArrayFromPCMBinaryBuffer = (b64EncodedBuffer: string) => {
47-
const b64DecodedChunk = Buffer.from(b64EncodedBuffer, 'base64');
48-
const int16Array = new Int16Array(b64DecodedChunk.buffer);
49-
50-
const float32Array = new Float32Array(int16Array.length);
51-
for (let i = 0; i < int16Array.length; i++) {
52-
float32Array[i] = Math.max(
53-
-1,
54-
Math.min(1, (int16Array[i] / audioStreamOptions.bufferSize) * 8)
55-
);
56-
}
57-
return float32Array;
58-
};
5925

6026
export default function LLMScreen({
6127
setIsGenerating,
6228
}: {
6329
setIsGenerating: React.Dispatch<React.SetStateAction<boolean>>;
6430
}) {
65-
const [isRecording, setIsRecording] = useState(false);
6631
const [isTextInputFocused, setIsTextInputFocused] = useState(false);
6732
const [userInput, setUserInput] = useState('');
6833
const textInputRef = useRef<TextInput>(null);
69-
const messageRecorded = useRef<boolean>(false);
7034

7135
const llm = useLLM({
7236
modelSource: QWEN3_0_6B_QUANTIZED,
7337
tokenizerSource: QWEN3_TOKENIZER,
7438
tokenizerConfigSource: QWEN3_TOKENIZER_CONFIG,
75-
chatConfig: {
76-
contextWindowLength: 6,
77-
},
78-
});
79-
const speechToText = useSpeechToText({
80-
modelName: 'moonshine',
81-
windowSize: 3,
82-
overlapSeconds: 1.2,
8339
});
8440

85-
const onChunk = (data: string) => {
86-
const float32Chunk = float32ArrayFromPCMBinaryBuffer(data);
87-
speechToText.streamingTranscribe(
88-
STREAMING_ACTION.DATA,
89-
Array.from(float32Chunk)
90-
);
91-
};
92-
93-
const handleRecordPress = async () => {
94-
if (isRecording) {
95-
setIsRecording(false);
96-
LiveAudioStream.stop();
97-
messageRecorded.current = true;
98-
await llm.sendMessage(
99-
await speechToText.streamingTranscribe(STREAMING_ACTION.STOP)
100-
);
101-
} else {
102-
setIsRecording(true);
103-
startStreamingAudio(audioStreamOptions, onChunk);
104-
await speechToText.streamingTranscribe(STREAMING_ACTION.START);
105-
}
106-
};
107-
10841
useEffect(() => {
10942
setIsGenerating(llm.isGenerating);
11043
}, [llm.isGenerating, setIsGenerating]);
@@ -118,10 +51,10 @@ export default function LLMScreen({
11851
}
11952
};
12053

121-
return !llm.isReady || !speechToText.isReady ? (
54+
return !llm.isReady ? (
12255
<Spinner
123-
visible={!llm.isReady || !speechToText.isReady}
124-
textContent={`Loading the model ${(llm.downloadProgress * 100).toFixed(0)} %\nLoading the speech model ${(speechToText.downloadProgress * 100).toFixed(0)} %`}
56+
visible={!llm.isReady}
57+
textContent={`Loading the model ${(llm.downloadProgress * 100).toFixed(0)} %\n`}
12558
/>
12659
) : (
12760
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
@@ -134,17 +67,10 @@ export default function LLMScreen({
13467
<SWMIcon width={45} height={45} />
13568
<Text style={styles.textModelName}>Qwen 3 x Whisper</Text>
13669
</View>
137-
{llm.messageHistory.length || speechToText.sequence ? (
70+
{llm.messageHistory.length ? (
13871
<View style={styles.chatContainer}>
13972
<Messages
140-
chatHistory={
141-
speechToText.isGenerating
142-
? [
143-
...llm.messageHistory,
144-
{ role: 'user', content: speechToText.sequence },
145-
]
146-
: llm.messageHistory
147-
}
73+
chatHistory={llm.messageHistory}
14874
llmResponse={llm.response}
14975
isGenerating={llm.isGenerating}
15076
deleteMessage={llm.deleteMessage}
@@ -162,13 +88,13 @@ export default function LLMScreen({
16288
<TextInput
16389
onFocus={() => setIsTextInputFocused(true)}
16490
onBlur={() => setIsTextInputFocused(false)}
165-
editable={!isRecording && !llm.isGenerating}
91+
editable={!!llm.isGenerating}
16692
style={{
16793
...styles.textInput,
16894
borderColor: isTextInputFocused
16995
? ColorPalette.blueDark
17096
: ColorPalette.blueLight,
171-
display: isRecording ? 'none' : 'flex',
97+
display: 'flex',
17298
}}
17399
placeholder="Your message"
174100
placeholderTextColor={'#C1C6E5'}
@@ -180,19 +106,6 @@ export default function LLMScreen({
180106
<TouchableOpacity onPress={llm.interrupt}>
181107
<PauseIcon height={40} width={40} padding={4} margin={8} />
182108
</TouchableOpacity>
183-
) : !userInput ? (
184-
<TouchableOpacity
185-
style={
186-
!isRecording ? styles.recordTouchable : styles.recordingInfo
187-
}
188-
onPress={handleRecordPress}
189-
>
190-
{isRecording ? (
191-
<StopIcon height={40} width={40} padding={4} margin={8} />
192-
) : (
193-
<MicIcon height={40} width={40} padding={4} margin={8} />
194-
)}
195-
</TouchableOpacity>
196109
) : (
197110
<TouchableOpacity
198111
style={styles.recordTouchable}
@@ -208,9 +121,6 @@ export default function LLMScreen({
208121
}
209122

210123
const styles = StyleSheet.create({
211-
container: {
212-
flex: 1,
213-
},
214124
keyboardAvoidingView: {
215125
flex: 1,
216126
},
@@ -262,20 +172,9 @@ const styles = StyleSheet.create({
262172
color: ColorPalette.primary,
263173
padding: 16,
264174
},
265-
fromUrlTouchable: {
266-
height: '100%',
267-
justifyContent: 'center',
268-
alignItems: 'flex-start',
269-
},
270175
recordTouchable: {
271176
height: '100%',
272177
justifyContent: 'center',
273178
alignItems: 'center',
274179
},
275-
recordingInfo: {
276-
width: '100%',
277-
display: 'flex',
278-
justifyContent: 'center',
279-
alignItems: 'center',
280-
},
281180
});

0 commit comments

Comments
 (0)