@@ -13,98 +13,31 @@ import {
1313import SWMIcon from '../assets/icons/swm_icon.svg' ;
1414import Spinner from 'react-native-loading-spinner-overlay' ;
1515import {
16- STREAMING_ACTION ,
17- useSpeechToText ,
1816 useLLM ,
1917 QWEN3_0_6B_QUANTIZED ,
2018 QWEN3_TOKENIZER ,
2119 QWEN3_TOKENIZER_CONFIG ,
2220} from 'react-native-executorch' ;
2321import PauseIcon from '../assets/icons/pause_icon.svg' ;
24- import MicIcon from '../assets/icons/mic_icon.svg' ;
2522import SendIcon from '../assets/icons/send_icon.svg' ;
26- import StopIcon from '../assets/icons/stop_icon.svg' ;
2723import ColorPalette from '../colors' ;
2824import 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
6026export 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
210123const 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