From 5bc11a0d88f47739361df6a4aa2415c1069ee7e1 Mon Sep 17 00:00:00 2001 From: pweglik Date: Mon, 16 Jun 2025 17:47:42 +0200 Subject: [PATCH 1/5] Added custom drawer content and forced reloads on Screen components when they go out of focus by introducing wrapers. --- apps/llm/app/_layout.tsx | 108 +++++++---- apps/llm/app/context.ts | 5 + apps/llm/app/index.tsx | 230 +++++++++++++++++------- apps/llm/app/llm/index.tsx | 172 ------------------ apps/llm/app/llm_tool_calling/index.tsx | 17 +- apps/llm/app/voice_chat/index.tsx | 17 +- apps/llm/ios/Podfile.lock | 140 +++++++-------- 7 files changed, 345 insertions(+), 344 deletions(-) create mode 100644 apps/llm/app/context.ts delete mode 100644 apps/llm/app/llm/index.tsx diff --git a/apps/llm/app/_layout.tsx b/apps/llm/app/_layout.tsx index 31cac307f..9552d04e8 100644 --- a/apps/llm/app/_layout.tsx +++ b/apps/llm/app/_layout.tsx @@ -1,50 +1,82 @@ import { Drawer } from 'expo-router/drawer'; import ColorPalette from '../colors'; -import React from 'react'; +import React, { useState } from 'react'; +import { Text } from 'react-native'; + +import { + DrawerContentComponentProps, + DrawerContentScrollView, + DrawerItemList, +} from '@react-navigation/drawer'; +import { GeneratingContext } from './context'; + +interface CustomDrawerProps extends DrawerContentComponentProps { + isGenerating: boolean; +} + +function CustomDrawerContent(props: CustomDrawerProps) { + const { isGenerating, ...otherProps } = props; + return ( + + {!isGenerating ? ( + + ) : ( + Model is generating. Interrupt before switching model + )} + + ); +} function DrawerWithScreens() { + const [isGenerating, setIsGenerating] = useState(false); + + console.log('isGlobalGenerating', isGenerating); + return ( - { + setIsGenerating(newState); + }, }} > - - - - ( + + )} + screenOptions={{ + drawerActiveTintColor: ColorPalette.primary, + drawerInactiveTintColor: '#888', + headerTintColor: ColorPalette.primary, headerTitleStyle: { color: ColorPalette.primary }, }} - /> - + > + + + + + ); } diff --git a/apps/llm/app/context.ts b/apps/llm/app/context.ts new file mode 100644 index 000000000..0eb056076 --- /dev/null +++ b/apps/llm/app/context.ts @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +export const GeneratingContext = createContext({ + setGlobalGenerating: (_newState: boolean) => {}, +}); diff --git a/apps/llm/app/index.tsx b/apps/llm/app/index.tsx index 1753552f8..2fa62bd37 100644 --- a/apps/llm/app/index.tsx +++ b/apps/llm/app/index.tsx @@ -1,75 +1,185 @@ -import { useRouter } from 'expo-router'; -import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; +import { useContext, useEffect, useRef, useState } from 'react'; +import { + Keyboard, + KeyboardAvoidingView, + StyleSheet, + Text, + TextInput, + Platform, + TouchableOpacity, + TouchableWithoutFeedback, + View, +} from 'react-native'; +import SendIcon from '../assets/icons/send_icon.svg'; +import Spinner from 'react-native-loading-spinner-overlay'; +import { + LLAMA3_2_1B_QLORA, + LLAMA3_2_TOKENIZER, + LLAMA3_2_TOKENIZER_CONFIG, + useLLM, +} from 'react-native-executorch'; +import PauseIcon from '../assets/icons/pause_icon.svg'; import ColorPalette from '../colors'; -import ExecutorchLogo from '../assets/icons/executorch.svg'; +import Messages from '../components/Messages'; +import { useIsFocused } from '@react-navigation/native'; +import { GeneratingContext } from './context'; -export default function Home() { - const router = useRouter(); +export default function LLMScreenWrapper() { + const isFocused = useIsFocused(); - return ( - - - Select a demo model - - router.navigate('llm/')} - > - LLM - - router.navigate('llm_tool_calling/')} - > - LLM Tool Calling - - router.navigate('voice_chat/')} - > - Voice Chat - - - - ); + return isFocused ? : null; } -export const fontSizes = { - xxl: 34, - xl: 22, - lg: 18, - md: 16, - sm: 14, - xs: 12, - xxs: 10, -}; +function LLMScreen() { + const [isTextInputFocused, setIsTextInputFocused] = useState(false); + const [userInput, setUserInput] = useState(''); + const textInputRef = useRef(null); + const { setGlobalGenerating } = useContext(GeneratingContext); + + const llm = useLLM({ + modelSource: LLAMA3_2_1B_QLORA, + tokenizerSource: LLAMA3_2_TOKENIZER, + tokenizerConfigSource: LLAMA3_2_TOKENIZER_CONFIG, + }); + + useEffect(() => { + if (llm.error) { + console.log('LLM error:', llm.error); + } + }, [llm.error]); + + useEffect(() => { + setGlobalGenerating(llm.isGenerating); + }, [llm.isGenerating, setGlobalGenerating]); + + const sendMessage = async () => { + setUserInput(''); + textInputRef.current?.clear(); + try { + await llm.sendMessage(userInput); + } catch (e) { + console.error(e); + } + }; + + return !llm.isReady ? ( + + ) : ( + + + + {llm.messageHistory.length ? ( + + + + ) : ( + + Hello! 👋 + + What can I help you with? + + + )} + + + setIsTextInputFocused(true)} + onBlur={() => setIsTextInputFocused(false)} + style={{ + ...styles.textInput, + borderColor: isTextInputFocused + ? ColorPalette.blueDark + : ColorPalette.blueLight, + }} + placeholder="Your message" + placeholderTextColor={'#C1C6E5'} + multiline={true} + ref={textInputRef} + onChangeText={(text: string) => setUserInput(text)} + /> + {userInput && ( + !llm.isGenerating && (await sendMessage())} + > + + + )} + {llm.isGenerating && ( + + + + )} + + + + + ); +} const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', + keyboardAvoidingView: { flex: 1 }, + container: { flex: 1 }, + chatContainer: { flex: 10, width: '100%' }, + helloMessageContainer: { + flex: 10, + width: '100%', alignItems: 'center', - backgroundColor: '#fff', + justifyContent: 'center', }, - headerText: { - fontSize: fontSizes.lg, - color: ColorPalette.strongPrimary, - margin: 20, + helloText: { + fontFamily: 'medium', + fontSize: 30, + color: ColorPalette.primary, }, - buttonContainer: { - width: '80%', - justifyContent: 'space-evenly', - marginBottom: 20, + bottomHelloText: { + fontFamily: 'regular', + fontSize: 20, + lineHeight: 28, + color: ColorPalette.primary, }, - button: { - backgroundColor: ColorPalette.strongPrimary, - borderRadius: 8, - padding: 10, + bottomContainer: { + height: 100, + width: '100%', + flexDirection: 'row', + justifyContent: 'space-between', alignItems: 'center', - marginBottom: 10, + paddingHorizontal: 16, }, - buttonText: { - color: 'white', - fontSize: fontSizes.md, + textInput: { + flex: 1, + borderWidth: 1, + borderRadius: 8, + lineHeight: 19.6, + fontFamily: 'regular', + fontSize: 14, + color: ColorPalette.primary, + padding: 16, + }, + sendChatTouchable: { + height: '100%', + width: 48, + justifyContent: 'center', + alignItems: 'flex-end', }, }); diff --git a/apps/llm/app/llm/index.tsx b/apps/llm/app/llm/index.tsx deleted file mode 100644 index f406b3c4a..000000000 --- a/apps/llm/app/llm/index.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import { - Keyboard, - KeyboardAvoidingView, - StyleSheet, - Text, - TextInput, - Platform, - TouchableOpacity, - TouchableWithoutFeedback, - View, -} from 'react-native'; -import SendIcon from '../../assets/icons/send_icon.svg'; -import Spinner from 'react-native-loading-spinner-overlay'; -import { - LLAMA3_2_1B_QLORA, - LLAMA3_2_TOKENIZER, - LLAMA3_2_TOKENIZER_CONFIG, - useLLM, -} from 'react-native-executorch'; -import PauseIcon from '../../assets/icons/pause_icon.svg'; -import ColorPalette from '../../colors'; -import Messages from '../../components/Messages'; - -export default function LLMScreen() { - const [isTextInputFocused, setIsTextInputFocused] = useState(false); - const [userInput, setUserInput] = useState(''); - const textInputRef = useRef(null); - - const llm = useLLM({ - modelSource: LLAMA3_2_1B_QLORA, - tokenizerSource: LLAMA3_2_TOKENIZER, - tokenizerConfigSource: LLAMA3_2_TOKENIZER_CONFIG, - }); - - useEffect(() => { - if (llm.error) { - console.log('LLM error:', llm.error); - } - }, [llm.error]); - - const sendMessage = async () => { - setUserInput(''); - textInputRef.current?.clear(); - try { - await llm.sendMessage(userInput); - } catch (e) { - console.error(e); - } - }; - - return !llm.isReady ? ( - - ) : ( - - - - {llm.messageHistory.length ? ( - - - - ) : ( - - Hello! 👋 - - What can I help you with? - - - )} - - - setIsTextInputFocused(true)} - onBlur={() => setIsTextInputFocused(false)} - style={{ - ...styles.textInput, - borderColor: isTextInputFocused - ? ColorPalette.blueDark - : ColorPalette.blueLight, - }} - placeholder="Your message" - placeholderTextColor={'#C1C6E5'} - multiline={true} - ref={textInputRef} - onChangeText={(text: string) => setUserInput(text)} - /> - {userInput && ( - !llm.isGenerating && (await sendMessage())} - > - - - )} - {llm.isGenerating && ( - - - - )} - - - - - ); -} - -const styles = StyleSheet.create({ - keyboardAvoidingView: { flex: 1 }, - container: { flex: 1 }, - chatContainer: { flex: 10, width: '100%' }, - helloMessageContainer: { - flex: 10, - width: '100%', - alignItems: 'center', - justifyContent: 'center', - }, - helloText: { - fontFamily: 'medium', - fontSize: 30, - color: ColorPalette.primary, - }, - bottomHelloText: { - fontFamily: 'regular', - fontSize: 20, - lineHeight: 28, - color: ColorPalette.primary, - }, - bottomContainer: { - height: 100, - width: '100%', - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingHorizontal: 16, - }, - textInput: { - flex: 1, - borderWidth: 1, - borderRadius: 8, - lineHeight: 19.6, - fontFamily: 'regular', - fontSize: 14, - color: ColorPalette.primary, - padding: 16, - }, - sendChatTouchable: { - height: '100%', - width: 48, - justifyContent: 'center', - alignItems: 'flex-end', - }, -}); diff --git a/apps/llm/app/llm_tool_calling/index.tsx b/apps/llm/app/llm_tool_calling/index.tsx index fe767997c..1597b423a 100644 --- a/apps/llm/app/llm_tool_calling/index.tsx +++ b/apps/llm/app/llm_tool_calling/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useContext, useEffect, useRef, useState } from 'react'; import { Keyboard, KeyboardAvoidingView, @@ -26,11 +26,20 @@ import Messages from '../../components/Messages'; import * as Brightness from 'expo-brightness'; import * as Calendar from 'expo-calendar'; import { executeTool, TOOL_DEFINITIONS_PHONE } from '../../utils/tools'; +import { useIsFocused } from '@react-navigation/native'; +import { GeneratingContext } from '../context'; -export default function LLMToolCallingScreen() { +export default function LLMToolCallingScreenWrapper() { + const isFocused = useIsFocused(); + + return isFocused ? : null; +} + +function LLMToolCallingScreen() { const [isTextInputFocused, setIsTextInputFocused] = useState(false); const [userInput, setUserInput] = useState(''); const textInputRef = useRef(null); + const { setGlobalGenerating } = useContext(GeneratingContext); const llm = useLLM({ modelSource: HAMMER2_1_1_5B, @@ -38,6 +47,10 @@ export default function LLMToolCallingScreen() { tokenizerConfigSource: HAMMER2_1_TOKENIZER_CONFIG, }); + useEffect(() => { + setGlobalGenerating(llm.isGenerating); + }, [llm.isGenerating, setGlobalGenerating]); + const { configure } = llm; useEffect(() => { configure({ diff --git a/apps/llm/app/voice_chat/index.tsx b/apps/llm/app/voice_chat/index.tsx index fe3c19b73..eb55d125f 100644 --- a/apps/llm/app/voice_chat/index.tsx +++ b/apps/llm/app/voice_chat/index.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react'; +import { useContext, useEffect, useRef, useState } from 'react'; import { Keyboard, KeyboardAvoidingView, @@ -27,6 +27,8 @@ import Messages from '../../components/Messages'; import LiveAudioStream from 'react-native-live-audio-stream'; 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, @@ -55,9 +57,16 @@ const float32ArrayFromPCMBinaryBuffer = (b64EncodedBuffer: string) => { return float32Array; }; -export default function VoiceChatScreen() { +export default function VoiceChatScreenWrapper() { + const isFocused = useIsFocused(); + + return isFocused ? : null; +} + +function VoiceChatScreen() { const [isRecording, setIsRecording] = useState(false); const messageRecorded = useRef(false); + const { setGlobalGenerating } = useContext(GeneratingContext); const llm = useLLM({ modelSource: QWEN3_0_6B_QUANTIZED, @@ -70,6 +79,10 @@ export default function VoiceChatScreen() { overlapSeconds: 1.2, }); + useEffect(() => { + setGlobalGenerating(llm.isGenerating || speechToText.isGenerating); + }, [llm.isGenerating, speechToText.isGenerating, setGlobalGenerating]); + const onChunk = (data: string) => { const float32Chunk = float32ArrayFromPCMBinaryBuffer(data); speechToText.streamingTranscribe( diff --git a/apps/llm/ios/Podfile.lock b/apps/llm/ios/Podfile.lock index 3278a532c..16a673f2a 100644 --- a/apps/llm/ios/Podfile.lock +++ b/apps/llm/ios/Podfile.lock @@ -2444,97 +2444,97 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb - EXConstants: 9f310f44bfedba09087042756802040e464323c0 - Expo: 4e8bda07d30b024b1732f87843a5349a3ecc1316 - ExpoAsset: 3bc9adb7dbbf27ae82c18ca97eb988a3ae7e73b1 - ExpoBrightness: c335c6ccc082d5249a4b38dba5cd9a08aa0bf62b - ExpoCalendar: f5f94ea8dcd957b1434beb4e1c0da1af063322e6 - ExpoFileSystem: c36eb8155eb2381c83dda7dc210e3eec332368b6 - ExpoFont: abbb91a911eb961652c2b0a22eef801860425ed6 - ExpoHead: af044f3e9c99e7d8d21bf653b4c2f2ef53a7f082 - ExpoKeepAwake: bf0811570c8da182bfb879169437d4de298376e7 - ExpoLinking: b85ff4eafeae6fc638c6cace60007ae521af0ef4 - ExpoModulesCore: d431ffe83c8673d02cb38425594a5f5480fd3061 + EXConstants: be238322d57d084dc055dbd5d6fe6479510504ce + Expo: 77b39f42396989cbe6fbef9f6fafc9b35186a95b + ExpoAsset: 3ea3275cca6a7793b3d36fbf1075c590f803fbcb + ExpoBrightness: 05e750736f8886dcf235212b0caf85b0f605fc88 + ExpoCalendar: 660542dc1c5ef98f46bedcc8745aa707df5d501a + ExpoFileSystem: 3a98ca2a6f13674ecfd97327d1b44a8ace444cbd + ExpoFont: 312c73403bbd4f98e1d6a5330641a56292583cd2 + ExpoHead: 5df88545652c2d3a3ea50bcd7f6be6ca935ac997 + ExpoKeepAwake: e8dedc115d9f6f24b153ccd2d1d8efcdfd68a527 + ExpoLinking: 5d151d4a497d7e375308602f0a89b4e8acf7b5f8 + ExpoModulesCore: e2e363bcdee87b46f858586d1887ebb215582001 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 84b955f7b4da8b895faf5946f73748267347c975 fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: 314be5250afa5692b57b4dd1705959e1973a8ebe opencv-rne: 2305807573b6e29c8c87e3416ab096d09047a7a0 - RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 + RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 RCTDeprecation: 83ffb90c23ee5cea353bd32008a7bca100908f8c RCTRequired: eb7c0aba998009f47a540bec9e9d69a54f68136e RCTTypeSafety: 659ae318c09de0477fd27bbc9e140071c7ea5c93 React: c2d3aa44c49bb34e4dfd49d3ee92da5ebacc1c1c React-callinvoker: 1bdfb7549b5af266d85757193b5069f60659ef9d - React-Core: 10597593fdbae06f0089881e025a172e51d4a769 - React-CoreModules: 6907b255529dd46895cf687daa67b24484a612c2 - React-cxxreact: a9f5b8180d6955bc3f6a3fcd657c4d9b4d95c1f6 + React-Core: 7150cf9b6a5af063b37003062689f1691e79c020 + React-CoreModules: 15a85e6665d61678942da6ae485b351f4c699049 + React-cxxreact: 74f9de59259ac951923f5726aa14f0398f167af9 React-debug: e74e76912b91e08d580c481c34881899ccf63da9 - React-defaultsnativemodule: 11f6ee2cf69bf3af9d0f28a6253def33d21b5266 - React-domnativemodule: f940bbc4fa9e134190acbf3a4a9f95621b5a8f51 - React-Fabric: 6f5c357bf3a42ff11f8844ad3fc7a1eb04f4b9de - React-FabricComponents: 10e0c0209822ac9e69412913a8af1ca33573379b - React-FabricImage: f582e764072dfa4715ae8c42979a5bace9cbcc12 + React-defaultsnativemodule: 628285212bbd65417d40ad6a9f8781830fda6c98 + React-domnativemodule: 185d9808198405c176784aaf33403d713bd24fb7 + React-Fabric: c814804affbe1952e16149ddd20256e1bccae67e + React-FabricComponents: 81ef47d596966121784afec9924f9562a29b1691 + React-FabricImage: f14f371d678aa557101def954ac3ba27e48948ff React-featureflags: d5facceff8f8f6de430e0acecf4979a9a0839ba9 - React-featureflagsnativemodule: a7dd141f1ef4b7c1331af0035689fbc742a49ff4 - React-graphics: 36ae3407172c1c77cea29265d2b12b90aaef6aa0 - React-hermes: 9116d4e6d07abeb519a2852672de087f44da8f12 - React-idlecallbacksnativemodule: ae7f5ffc6cf2d2058b007b78248e5b08172ad5c3 - React-ImageManager: 9daee0dc99ad6a001d4b9e691fbf37107e2b7b54 - React-jserrorhandler: 1e6211581071edaf4ecd5303147328120c73f4dc - React-jsi: 753ba30c902f3a41fa7f956aca8eea3317a44ee6 - React-jsiexecutor: 47520714aa7d9589c51c0f3713dfbfca4895d4f9 - React-jsinspector: cfd27107f6d6f1076a57d88c932401251560fe5f - React-jsinspectortracing: 76a7d791f3c0c09a0d2bf6f46dfb0e79a4fcc0ac - React-jsitooling: 995e826570dd58f802251490486ebd3244a037ab - React-jsitracing: 094ae3d8c123cea67b50211c945b7c0443d3e97b - React-logger: 8edfcedc100544791cd82692ca5a574240a16219 - React-Mapbuffer: c3f4b608e4a59dd2f6a416ef4d47a14400194468 - React-microtasksnativemodule: 054f34e9b82f02bd40f09cebd4083828b5b2beb6 - react-native-executorch: 9e4c26039a419f8b79c040571e80617c1114ca26 - react-native-safe-area-context: 562163222d999b79a51577eda2ea8ad2c32b4d06 - React-NativeModulesApple: 2c4377e139522c3d73f5df582e4f051a838ff25e + React-featureflagsnativemodule: 96f0ab285382d95c90f663e02526a5ceefa95a11 + React-graphics: 1a66ee0a3f093b125b853f6370296fadcaf6f233 + React-hermes: 8b86e5f54a65ecb69cdf22b3a00a11562eda82d2 + React-idlecallbacksnativemodule: 5c25ab145c602264d00cb26a397ab52e0efa031c + React-ImageManager: 15e34bd5ef1ac4a18e96660817ef70a7f99ee8c2 + React-jserrorhandler: 02cdf2cd45350108be1ffd2b164578936dbbdff7 + React-jsi: 6af1987cfbb1b6621664fdbf6c7b62bd4d38c923 + React-jsiexecutor: 51f372998e0303585cb0317232b938d694663cbd + React-jsinspector: 3539ad976d073bfaa8a7d2fa9bef35e70e55033e + React-jsinspectortracing: e8dbacaf67c201f23052ca1c2bae2f7b84dec443 + React-jsitooling: 95a34f41e3c249d42181de13b4f8d854f178ca9f + React-jsitracing: 25b029cf5cad488252d46da19dd8c4c134fd5fe4 + React-logger: 368570a253f00879a1e4fea24ed4047e72e7bbf3 + React-Mapbuffer: c04fcda1c6281fc0a6824c7dcc1633dd217ac1ec + React-microtasksnativemodule: ca2804a25fdcefffa0aa942aa23ab53b99614a34 + react-native-executorch: 66ffc33df70ec85bc591f9ee34c862835966ead3 + react-native-safe-area-context: 00d03dc688ba86664be66f9e3f203fc7d747d899 + React-NativeModulesApple: 452b86b29fae99ed0a4015dca3ad9cd222f88abf React-oscompat: ef5df1c734f19b8003e149317d041b8ce1f7d29c - React-perflogger: 9a151e0b4c933c9205fd648c246506a83f31395d - React-performancetimeline: 5b0dfc0acba29ea0269ddb34cd6dd59d3b8a1c66 + React-perflogger: 6fd2f6811533e9c19a61e855c3033eecbf4ad2a0 + React-performancetimeline: abf31259d794c9274b3ea19c5016186925eec6c4 React-RCTActionSheet: a499b0d6d9793886b67ba3e16046a3fef2cdbbc3 - React-RCTAnimation: cc64adc259aabc3354b73065e2231d796dfce576 - React-RCTAppDelegate: 9d523da768f1c9e84c5f3b7e3624d097dfb0e16b - React-RCTBlob: e727f53eeefded7e6432eb76bd22b57bc880e5d1 - React-RCTFabric: 58590aa4fdb4ad546c06a7449b486cf6844e991f - React-RCTFBReactNativeSpec: 9064c63d99e467a3893e328ba3612745c3c3a338 - React-RCTImage: 7159cbdbb18a09d97ba1a611416eced75b3ccb29 - React-RCTLinking: 46293afdb859bccc63e1d3dedc6901a3c04ef360 - React-RCTNetwork: 4a6cd18f5bcd0363657789c64043123a896b1170 - React-RCTRuntime: 5ab904fd749aa52f267ef771d265612582a17880 - React-RCTSettings: 61e361dc85136d1cb0e148b7541993d2ee950ea7 - React-RCTText: abd1e196c3167175e6baef18199c6d9d8ac54b4e - React-RCTVibration: 490e0dcb01a3fe4a0dfb7bc51ad5856d8b84f343 + React-RCTAnimation: 2595dcb10a82216a511b54742f8c28d793852ac6 + React-RCTAppDelegate: f03604b70f57c9469a84a159d8abecf793a5bcff + React-RCTBlob: e00f9b4e2f151938f4d9864cf33ebf24ac03328a + React-RCTFabric: 3945d116fd271598db262d4e6ed5691d431ed9e8 + React-RCTFBReactNativeSpec: 0f4d4f0da938101f2ca9d5333a8f46e527ad2819 + React-RCTImage: dac5e9f8ec476aefe6e60ee640ebc1dfaf1a4dbe + React-RCTLinking: 494b785a40d952a1dfbe712f43214376e5f0e408 + React-RCTNetwork: b3d7c30cd21793e268db107dd0980cb61b3c1c44 + React-RCTRuntime: a8ff419d437228e7b8a793b14f9d711e1cbb82af + React-RCTSettings: a060c7e381a3896104761b8eed7e284d95e37df3 + React-RCTText: 4f272b72dbb61f390d8c8274528f9fdbff983806 + React-RCTVibration: 0e5326220719aca12473d703aa46693e3b4ce67a React-rendererconsistency: 351fdbc5c1fe4da24243d939094a80f0e149c7a1 - React-renderercss: 3438814bee838ae7840a633ab085ac81699fd5cf - React-rendererdebug: 0ac2b9419ad6f88444f066d4b476180af311fb1e + React-renderercss: d333f2ada83969591100d91ec6b23ca2e17e1507 + React-rendererdebug: 039e5949b72ba63c703de020701e3fd152434c61 React-rncore: 57ed480649bb678d8bdc386d20fee8bf2b0c307c - React-RuntimeApple: 8b7a9788f31548298ba1990620fe06b40de65ad7 - React-RuntimeCore: e03d96fbd57ce69fd9bca8c925942194a5126dbc + React-RuntimeApple: 344a5e1105256000afabaa8df12c3e4cab880340 + React-RuntimeCore: 0e48fb5e5160acc0334c7a723a42d42cef4b58b6 React-runtimeexecutor: d60846710facedd1edb70c08b738119b3ee2c6c2 - React-RuntimeHermes: aab794755d9f6efd249b61f3af4417296904e3ba - React-runtimescheduler: c3cd124fa5db7c37f601ee49ca0d97019acd8788 + React-RuntimeHermes: 064286a03871d932c99738e0f8ef854962ab4b99 + React-runtimescheduler: e917ab17ae08c204af1ebf8f669b7e411b0220c8 React-timing: a90f4654cbda9c628614f9bee68967f1768bd6a5 - React-utils: a612d50555b6f0f90c74b7d79954019ad47f5de6 - ReactAppDependencyProvider: 04d5eb15eb46be6720e17a4a7fa92940a776e584 - ReactCodegen: 7ea266ccd94436294f516247db7402b57b1214af - ReactCommon: 76d2dc87136d0a667678668b86f0fca0c16fdeb0 - RNAudioAPI: 2e3fd4bf75aa5717791babb30126707504996f09 - RNDeviceInfo: d863506092aef7e7af3a1c350c913d867d795047 - RNGestureHandler: 7d0931a61d7ba0259f32db0ba7d0963c3ed15d2b - RNLiveAudioStream: 93ac2bb6065be9018d0b00157b220f11cebc1513 - RNReanimated: afd6a269a47d6f13ba295c46c6c0e14e3cbd0d8a - RNScreens: 482e9707f9826230810c92e765751af53826d509 - RNSVG: 794f269526df9ddc1f79b3d1a202b619df0368e3 + React-utils: 51c4e71608b8133fecc9a15801d244ae7bdf3758 + ReactAppDependencyProvider: d5dcc564f129632276bd3184e60f053fcd574d6b + ReactCodegen: c9a256facbe4996140f3fb95c7f03ba61c12acc9 + ReactCommon: 4d0da92a5eb8da86c08e3ec34bd23ab439fb2461 + RNAudioAPI: f93e51adeee0911c8c6629a56f6df35edc60c084 + RNDeviceInfo: feea80a690d2bde1fe51461cf548039258bd03f2 + RNGestureHandler: ccf4105b125002bd88e39d2a1f2b7e6001bcdf34 + RNLiveAudioStream: 02584d52711b6b9f268cb371a4b1bdd76ab3e079 + RNReanimated: c567de23384730756bb19ff55490819980536b09 + RNScreens: c2e3cc506212228c607b4785b315205e28acbf0f + RNSVG: ee32efbed652c5151fd3f98bed13c68af285bc38 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 sqlite3: 1d85290c3321153511f6e900ede7a1608718bbd5 - Yoga: c758bfb934100bb4bf9cbaccb52557cee35e8bdf + Yoga: 9f110fc4b7aa538663cba3c14cbb1c335f43c13f PODFILE CHECKSUM: bba19a069e673f2259009e9d2caab44374fdebcf From 07fd6277aa554cff23bb8992575b7e1c3cfb8c0d Mon Sep 17 00:00:00 2001 From: pweglik Date: Tue, 17 Jun 2025 11:11:31 +0200 Subject: [PATCH 2/5] Fixes --- apps/llm/app/_layout.tsx | 16 +- apps/llm/app/index.tsx | 230 +++++++----------------- apps/llm/app/llm/index.tsx | 185 +++++++++++++++++++ apps/llm/app/llm_tool_calling/index.tsx | 2 +- apps/llm/app/voice_chat/index.tsx | 2 +- apps/llm/{app => }/context.ts | 0 6 files changed, 256 insertions(+), 179 deletions(-) create mode 100644 apps/llm/app/llm/index.tsx rename apps/llm/{app => }/context.ts (100%) diff --git a/apps/llm/app/_layout.tsx b/apps/llm/app/_layout.tsx index 9552d04e8..f78e3ea90 100644 --- a/apps/llm/app/_layout.tsx +++ b/apps/llm/app/_layout.tsx @@ -8,7 +8,7 @@ import { DrawerContentScrollView, DrawerItemList, } from '@react-navigation/drawer'; -import { GeneratingContext } from './context'; +import { GeneratingContext } from '../context'; interface CustomDrawerProps extends DrawerContentComponentProps { isGenerating: boolean; @@ -27,7 +27,7 @@ function CustomDrawerContent(props: CustomDrawerProps) { ); } -function DrawerWithScreens() { +export default function _layout() { const [isGenerating, setIsGenerating] = useState(false); console.log('isGlobalGenerating', isGenerating); @@ -52,7 +52,7 @@ function DrawerWithScreens() { }} > + null, + }} + /> ); } - -export default function _layout() { - return ; -} diff --git a/apps/llm/app/index.tsx b/apps/llm/app/index.tsx index 2fa62bd37..5beaa15ba 100644 --- a/apps/llm/app/index.tsx +++ b/apps/llm/app/index.tsx @@ -1,185 +1,75 @@ -import { useContext, useEffect, useRef, useState } from 'react'; -import { - Keyboard, - KeyboardAvoidingView, - StyleSheet, - Text, - TextInput, - Platform, - TouchableOpacity, - TouchableWithoutFeedback, - View, -} from 'react-native'; -import SendIcon from '../assets/icons/send_icon.svg'; -import Spinner from 'react-native-loading-spinner-overlay'; -import { - LLAMA3_2_1B_QLORA, - LLAMA3_2_TOKENIZER, - LLAMA3_2_TOKENIZER_CONFIG, - useLLM, -} from 'react-native-executorch'; -import PauseIcon from '../assets/icons/pause_icon.svg'; +import { useRouter } from 'expo-router'; +import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; import ColorPalette from '../colors'; -import Messages from '../components/Messages'; -import { useIsFocused } from '@react-navigation/native'; -import { GeneratingContext } from './context'; +import ExecutorchLogo from '../assets/icons/executorch.svg'; -export default function LLMScreenWrapper() { - const isFocused = useIsFocused(); +export default function App() { + const router = useRouter(); - return isFocused ? : null; -} - -function LLMScreen() { - const [isTextInputFocused, setIsTextInputFocused] = useState(false); - const [userInput, setUserInput] = useState(''); - const textInputRef = useRef(null); - const { setGlobalGenerating } = useContext(GeneratingContext); - - const llm = useLLM({ - modelSource: LLAMA3_2_1B_QLORA, - tokenizerSource: LLAMA3_2_TOKENIZER, - tokenizerConfigSource: LLAMA3_2_TOKENIZER_CONFIG, - }); - - useEffect(() => { - if (llm.error) { - console.log('LLM error:', llm.error); - } - }, [llm.error]); - - useEffect(() => { - setGlobalGenerating(llm.isGenerating); - }, [llm.isGenerating, setGlobalGenerating]); - - const sendMessage = async () => { - setUserInput(''); - textInputRef.current?.clear(); - try { - await llm.sendMessage(userInput); - } catch (e) { - console.error(e); - } - }; - - return !llm.isReady ? ( - - ) : ( - - - - {llm.messageHistory.length ? ( - - - - ) : ( - - Hello! 👋 - - What can I help you with? - - - )} - - - setIsTextInputFocused(true)} - onBlur={() => setIsTextInputFocused(false)} - style={{ - ...styles.textInput, - borderColor: isTextInputFocused - ? ColorPalette.blueDark - : ColorPalette.blueLight, - }} - placeholder="Your message" - placeholderTextColor={'#C1C6E5'} - multiline={true} - ref={textInputRef} - onChangeText={(text: string) => setUserInput(text)} - /> - {userInput && ( - !llm.isGenerating && (await sendMessage())} - > - - - )} - {llm.isGenerating && ( - - - - )} - - - - + return ( + + + Select a demo model + + router.navigate('app/llm/')} + > + LLM + + router.navigate('app/llm_tool_calling/')} + > + LLM Tool Calling + + router.navigate('app/voice_chat/')} + > + Voice Chat + + + ); } +export const fontSizes = { + xxl: 34, + xl: 22, + lg: 18, + md: 16, + sm: 14, + xs: 12, + xxs: 10, +}; + const styles = StyleSheet.create({ - keyboardAvoidingView: { flex: 1 }, - container: { flex: 1 }, - chatContainer: { flex: 10, width: '100%' }, - helloMessageContainer: { - flex: 10, - width: '100%', - alignItems: 'center', + container: { + flex: 1, justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#fff', }, - helloText: { - fontFamily: 'medium', - fontSize: 30, - color: ColorPalette.primary, - }, - bottomHelloText: { - fontFamily: 'regular', - fontSize: 20, - lineHeight: 28, - color: ColorPalette.primary, + headerText: { + fontSize: fontSizes.lg, + color: ColorPalette.strongPrimary, + margin: 20, }, - bottomContainer: { - height: 100, - width: '100%', - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingHorizontal: 16, + buttonContainer: { + width: '80%', + justifyContent: 'space-evenly', + marginBottom: 20, }, - textInput: { - flex: 1, - borderWidth: 1, + button: { + backgroundColor: ColorPalette.strongPrimary, borderRadius: 8, - lineHeight: 19.6, - fontFamily: 'regular', - fontSize: 14, - color: ColorPalette.primary, - padding: 16, + padding: 10, + alignItems: 'center', + marginBottom: 10, }, - sendChatTouchable: { - height: '100%', - width: 48, - justifyContent: 'center', - alignItems: 'flex-end', + buttonText: { + color: 'white', + fontSize: fontSizes.md, }, }); diff --git a/apps/llm/app/llm/index.tsx b/apps/llm/app/llm/index.tsx new file mode 100644 index 000000000..35892c15d --- /dev/null +++ b/apps/llm/app/llm/index.tsx @@ -0,0 +1,185 @@ +import { useContext, useEffect, useRef, useState } from 'react'; +import { + Keyboard, + KeyboardAvoidingView, + StyleSheet, + Text, + TextInput, + Platform, + TouchableOpacity, + TouchableWithoutFeedback, + View, +} from 'react-native'; +import SendIcon from '../../assets/icons/send_icon.svg'; +import Spinner from 'react-native-loading-spinner-overlay'; +import { + LLAMA3_2_1B_QLORA, + LLAMA3_2_TOKENIZER, + LLAMA3_2_TOKENIZER_CONFIG, + useLLM, +} from 'react-native-executorch'; +import PauseIcon from '../../assets/icons/pause_icon.svg'; +import ColorPalette from '../../colors'; +import Messages from '../../components/Messages'; +import { useIsFocused } from '@react-navigation/native'; +import { GeneratingContext } from '../../context'; + +export default function LLMScreenWrapper() { + const isFocused = useIsFocused(); + + return isFocused ? : null; +} + +function LLMScreen() { + const [isTextInputFocused, setIsTextInputFocused] = useState(false); + const [userInput, setUserInput] = useState(''); + const textInputRef = useRef(null); + const { setGlobalGenerating } = useContext(GeneratingContext); + + const llm = useLLM({ + modelSource: LLAMA3_2_1B_QLORA, + tokenizerSource: LLAMA3_2_TOKENIZER, + tokenizerConfigSource: LLAMA3_2_TOKENIZER_CONFIG, + }); + + useEffect(() => { + if (llm.error) { + console.log('LLM error:', llm.error); + } + }, [llm.error]); + + useEffect(() => { + setGlobalGenerating(llm.isGenerating); + }, [llm.isGenerating, setGlobalGenerating]); + + const sendMessage = async () => { + setUserInput(''); + textInputRef.current?.clear(); + try { + await llm.sendMessage(userInput); + } catch (e) { + console.error(e); + } + }; + + return !llm.isReady ? ( + + ) : ( + + + + {llm.messageHistory.length ? ( + + + + ) : ( + + Hello! 👋 + + What can I help you with? + + + )} + + + setIsTextInputFocused(true)} + onBlur={() => setIsTextInputFocused(false)} + style={{ + ...styles.textInput, + borderColor: isTextInputFocused + ? ColorPalette.blueDark + : ColorPalette.blueLight, + }} + placeholder="Your message" + placeholderTextColor={'#C1C6E5'} + multiline={true} + ref={textInputRef} + onChangeText={(text: string) => setUserInput(text)} + /> + {userInput && ( + !llm.isGenerating && (await sendMessage())} + > + + + )} + {llm.isGenerating && ( + + + + )} + + + + + ); +} + +const styles = StyleSheet.create({ + keyboardAvoidingView: { flex: 1 }, + container: { flex: 1 }, + chatContainer: { flex: 10, width: '100%' }, + helloMessageContainer: { + flex: 10, + width: '100%', + alignItems: 'center', + justifyContent: 'center', + }, + helloText: { + fontFamily: 'medium', + fontSize: 30, + color: ColorPalette.primary, + }, + bottomHelloText: { + fontFamily: 'regular', + fontSize: 20, + lineHeight: 28, + color: ColorPalette.primary, + }, + bottomContainer: { + height: 100, + width: '100%', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 16, + }, + textInput: { + flex: 1, + borderWidth: 1, + borderRadius: 8, + lineHeight: 19.6, + fontFamily: 'regular', + fontSize: 14, + color: ColorPalette.primary, + padding: 16, + }, + sendChatTouchable: { + height: '100%', + width: 48, + justifyContent: 'center', + alignItems: 'flex-end', + }, +}); diff --git a/apps/llm/app/llm_tool_calling/index.tsx b/apps/llm/app/llm_tool_calling/index.tsx index 1597b423a..831fed224 100644 --- a/apps/llm/app/llm_tool_calling/index.tsx +++ b/apps/llm/app/llm_tool_calling/index.tsx @@ -27,7 +27,7 @@ import * as Brightness from 'expo-brightness'; import * as Calendar from 'expo-calendar'; import { executeTool, TOOL_DEFINITIONS_PHONE } from '../../utils/tools'; import { useIsFocused } from '@react-navigation/native'; -import { GeneratingContext } from '../context'; +import { GeneratingContext } from '../../context'; export default function LLMToolCallingScreenWrapper() { const isFocused = useIsFocused(); diff --git a/apps/llm/app/voice_chat/index.tsx b/apps/llm/app/voice_chat/index.tsx index eb55d125f..30683fec6 100644 --- a/apps/llm/app/voice_chat/index.tsx +++ b/apps/llm/app/voice_chat/index.tsx @@ -28,7 +28,7 @@ import LiveAudioStream from 'react-native-live-audio-stream'; import DeviceInfo from 'react-native-device-info'; import { Buffer } from 'buffer'; import { useIsFocused } from '@react-navigation/native'; -import { GeneratingContext } from '../context'; +import { GeneratingContext } from '../../context'; const audioStreamOptions = { sampleRate: 16000, channels: 1, diff --git a/apps/llm/app/context.ts b/apps/llm/context.ts similarity index 100% rename from apps/llm/app/context.ts rename to apps/llm/context.ts From 9c838de25d0b79fdaebb1441833328c1611a59fd Mon Sep 17 00:00:00 2001 From: pweglik Date: Tue, 17 Jun 2025 11:34:07 +0200 Subject: [PATCH 3/5] Fix to drawer + added CV app --- apps/computer-vision/app/_layout.tsx | 161 +++++++++++------- .../app/classification/index.tsx | 12 +- .../app/image_segmentation/index.tsx | 12 +- .../app/object_detection/index.tsx | 12 +- apps/computer-vision/app/ocr/index.tsx | 12 +- .../app/ocr_vertical/index.tsx | 12 +- .../app/style_transfer/index.tsx | 12 +- apps/computer-vision/context.ts | 5 + apps/computer-vision/screenWrapper.tsx | 8 + apps/llm/app/_layout.tsx | 3 +- 10 files changed, 167 insertions(+), 82 deletions(-) create mode 100644 apps/computer-vision/context.ts create mode 100644 apps/computer-vision/screenWrapper.tsx diff --git a/apps/computer-vision/app/_layout.tsx b/apps/computer-vision/app/_layout.tsx index f62075ea8..2fa8c1e3a 100644 --- a/apps/computer-vision/app/_layout.tsx +++ b/apps/computer-vision/app/_layout.tsx @@ -1,73 +1,110 @@ import { Drawer } from 'expo-router/drawer'; import ColorPalette from '../colors'; -import React from 'react'; +import React, { useState } from 'react'; +import { Text } from 'react-native'; + +import { + DrawerContentComponentProps, + DrawerContentScrollView, + DrawerItemList, +} from '@react-navigation/drawer'; +import { GeneratingContext } from '../context'; + +interface CustomDrawerProps extends DrawerContentComponentProps { + isGenerating: boolean; +} + +function CustomDrawerContent(props: CustomDrawerProps) { + const { isGenerating, ...otherProps } = props; + return ( + + {!isGenerating ? ( + + ) : ( + Model is generating. Interrupt before switching model + )} + + ); +} export default function _layout() { + const [isGenerating, setIsGenerating] = useState(false); + return ( - { + setIsGenerating(newState); + }, }} > - - - - - - - ( + + )} + screenOptions={{ + drawerActiveTintColor: ColorPalette.primary, + drawerInactiveTintColor: '#888', + headerTintColor: ColorPalette.primary, headerTitleStyle: { color: ColorPalette.primary }, }} - /> - + > + + + + + + + null, + drawerItemStyle: { display: 'none' }, + }} + /> + + ); } diff --git a/apps/computer-vision/app/classification/index.tsx b/apps/computer-vision/app/classification/index.tsx index 2e0ab4a1f..949c8a0da 100644 --- a/apps/computer-vision/app/classification/index.tsx +++ b/apps/computer-vision/app/classification/index.tsx @@ -1,9 +1,11 @@ -import { useState } from 'react'; import Spinner from 'react-native-loading-spinner-overlay'; import { getImage } from '../../utils'; import { useClassification, EFFICIENTNET_V2_S } from 'react-native-executorch'; import { View, StyleSheet, Image, Text, ScrollView } from 'react-native'; import { BottomBar } from '../../components/BottomBar'; +import React, { useContext, useEffect, useState } from 'react'; +import { GeneratingContext } from '../../context'; +import ScreenWrapper from '../../screenWrapper'; export default function ClassificationScreen() { const [results, setResults] = useState<{ label: string; score: number }[]>( @@ -14,6 +16,10 @@ export default function ClassificationScreen() { const model = useClassification({ modelSource: EFFICIENTNET_V2_S, }); + const { setGlobalGenerating } = useContext(GeneratingContext); + useEffect(() => { + setGlobalGenerating(model.isGenerating); + }, [model.isGenerating, setGlobalGenerating]); const handleCameraPress = async (isCamera: boolean) => { const image = await getImage(isCamera); @@ -48,7 +54,7 @@ export default function ClassificationScreen() { ); } return ( - <> + - + ); } diff --git a/apps/computer-vision/app/image_segmentation/index.tsx b/apps/computer-vision/app/image_segmentation/index.tsx index 48292e0fc..3b0685073 100644 --- a/apps/computer-vision/app/image_segmentation/index.tsx +++ b/apps/computer-vision/app/image_segmentation/index.tsx @@ -14,7 +14,9 @@ import { ColorType, } from '@shopify/react-native-skia'; import { View, StyleSheet, Image } from 'react-native'; -import { useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; +import { GeneratingContext } from '../../context'; +import ScreenWrapper from '../../screenWrapper'; const width = 224; const height = 224; @@ -62,6 +64,10 @@ export default function ImageSegmentationScreen() { const model = useImageSegmentation({ modelSource: DEEPLAB_V3_RESNET50, }); + const { setGlobalGenerating } = useContext(GeneratingContext); + useEffect(() => { + setGlobalGenerating(model.isGenerating); + }, [model.isGenerating, setGlobalGenerating]); const [imageUri, setImageUri] = useState(''); const handleCameraPress = async (isCamera: boolean) => { @@ -118,7 +124,7 @@ export default function ImageSegmentationScreen() { } return ( - <> + - + ); } diff --git a/apps/computer-vision/app/object_detection/index.tsx b/apps/computer-vision/app/object_detection/index.tsx index be8e31196..8124f9cfe 100644 --- a/apps/computer-vision/app/object_detection/index.tsx +++ b/apps/computer-vision/app/object_detection/index.tsx @@ -1,4 +1,3 @@ -import { useState } from 'react'; import Spinner from 'react-native-loading-spinner-overlay'; import { BottomBar } from '../../components/BottomBar'; import { getImage } from '../../utils'; @@ -9,6 +8,9 @@ import { } from 'react-native-executorch'; import { View, StyleSheet, Image } from 'react-native'; import ImageWithBboxes from '../../components/ImageWithBboxes'; +import React, { useContext, useEffect, useState } from 'react'; +import { GeneratingContext } from '../../context'; +import ScreenWrapper from '../../screenWrapper'; export default function ObjectDetectionScreen() { const [imageUri, setImageUri] = useState(''); @@ -21,6 +23,10 @@ export default function ObjectDetectionScreen() { const ssdLite = useObjectDetection({ modelSource: SSDLITE_320_MOBILENET_V3_LARGE, }); + const { setGlobalGenerating } = useContext(GeneratingContext); + useEffect(() => { + setGlobalGenerating(ssdLite.isGenerating); + }, [ssdLite.isGenerating, setGlobalGenerating]); const handleCameraPress = async (isCamera: boolean) => { const image = await getImage(isCamera); @@ -57,7 +63,7 @@ export default function ObjectDetectionScreen() { } return ( - <> + {imageUri && imageDimensions?.width && imageDimensions?.height ? ( @@ -82,7 +88,7 @@ export default function ObjectDetectionScreen() { handleCameraPress={handleCameraPress} runForward={runForward} /> - + ); } diff --git a/apps/computer-vision/app/ocr/index.tsx b/apps/computer-vision/app/ocr/index.tsx index f81216e5d..73c13915a 100644 --- a/apps/computer-vision/app/ocr/index.tsx +++ b/apps/computer-vision/app/ocr/index.tsx @@ -9,8 +9,10 @@ import { useOCR, } from 'react-native-executorch'; import { View, StyleSheet, Image, Text, ScrollView } from 'react-native'; -import { useState } from 'react'; import ImageWithBboxes2 from '../../components/ImageWithOCRBboxes'; +import React, { useContext, useEffect, useState } from 'react'; +import { GeneratingContext } from '../../context'; +import ScreenWrapper from '../../screenWrapper'; export default function OCRScreen() { const [imageUri, setImageUri] = useState(''); @@ -29,6 +31,10 @@ export default function OCRScreen() { }, language: 'en', }); + const { setGlobalGenerating } = useContext(GeneratingContext); + useEffect(() => { + setGlobalGenerating(model.isGenerating); + }, [model.isGenerating, setGlobalGenerating]); const handleCameraPress = async (isCamera: boolean) => { const image = await getImage(isCamera); @@ -62,7 +68,7 @@ export default function OCRScreen() { } return ( - <> + {imageUri && imageDimensions?.width && imageDimensions?.height ? ( @@ -100,7 +106,7 @@ export default function OCRScreen() { handleCameraPress={handleCameraPress} runForward={runForward} /> - + ); } diff --git a/apps/computer-vision/app/ocr_vertical/index.tsx b/apps/computer-vision/app/ocr_vertical/index.tsx index 7857e59c5..325bf7ac9 100644 --- a/apps/computer-vision/app/ocr_vertical/index.tsx +++ b/apps/computer-vision/app/ocr_vertical/index.tsx @@ -9,8 +9,10 @@ import { useVerticalOCR, } from 'react-native-executorch'; import { View, StyleSheet, Image, Text, ScrollView } from 'react-native'; -import { useState } from 'react'; import ImageWithBboxes2 from '../../components/ImageWithOCRBboxes'; +import React, { useContext, useEffect, useState } from 'react'; +import { GeneratingContext } from '../../context'; +import ScreenWrapper from '../../screenWrapper'; export default function VerticalOCRScree() { const [imageUri, setImageUri] = useState(''); @@ -31,6 +33,10 @@ export default function VerticalOCRScree() { language: 'en', independentCharacters: true, }); + const { setGlobalGenerating } = useContext(GeneratingContext); + useEffect(() => { + setGlobalGenerating(model.isGenerating); + }, [model.isGenerating, setGlobalGenerating]); const handleCameraPress = async (isCamera: boolean) => { const image = await getImage(isCamera); @@ -64,7 +70,7 @@ export default function VerticalOCRScree() { } return ( - <> + {imageUri && imageDimensions?.width && imageDimensions?.height ? ( @@ -102,7 +108,7 @@ export default function VerticalOCRScree() { handleCameraPress={handleCameraPress} runForward={runForward} /> - + ); } diff --git a/apps/computer-vision/app/style_transfer/index.tsx b/apps/computer-vision/app/style_transfer/index.tsx index 6c227b5a3..ba6d837ad 100644 --- a/apps/computer-vision/app/style_transfer/index.tsx +++ b/apps/computer-vision/app/style_transfer/index.tsx @@ -6,12 +6,18 @@ import { STYLE_TRANSFER_CANDY, } from 'react-native-executorch'; import { View, StyleSheet, Image } from 'react-native'; -import React, { useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; +import { GeneratingContext } from '../../context'; +import ScreenWrapper from '../../screenWrapper'; export default function StyleTransferScreen() { const model = useStyleTransfer({ modelSource: STYLE_TRANSFER_CANDY, }); + const { setGlobalGenerating } = useContext(GeneratingContext); + useEffect(() => { + setGlobalGenerating(model.isGenerating); + }, [model.isGenerating, setGlobalGenerating]); const [imageUri, setImageUri] = useState(''); const handleCameraPress = async (isCamera: boolean) => { const image = await getImage(isCamera); @@ -42,7 +48,7 @@ export default function StyleTransferScreen() { } return ( - <> + - + ); } diff --git a/apps/computer-vision/context.ts b/apps/computer-vision/context.ts new file mode 100644 index 000000000..0eb056076 --- /dev/null +++ b/apps/computer-vision/context.ts @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +export const GeneratingContext = createContext({ + setGlobalGenerating: (_newState: boolean) => {}, +}); diff --git a/apps/computer-vision/screenWrapper.tsx b/apps/computer-vision/screenWrapper.tsx new file mode 100644 index 000000000..0d1a9dae5 --- /dev/null +++ b/apps/computer-vision/screenWrapper.tsx @@ -0,0 +1,8 @@ +import { useIsFocused } from '@react-navigation/native'; +import { PropsWithChildren } from 'react'; + +export default function ScreenWrapper({ children }: PropsWithChildren) { + const isFocused = useIsFocused(); + + return isFocused ? <>{children} : null; +} diff --git a/apps/llm/app/_layout.tsx b/apps/llm/app/_layout.tsx index f78e3ea90..cc57b086d 100644 --- a/apps/llm/app/_layout.tsx +++ b/apps/llm/app/_layout.tsx @@ -30,8 +30,6 @@ function CustomDrawerContent(props: CustomDrawerProps) { export default function _layout() { const [isGenerating, setIsGenerating] = useState(false); - console.log('isGlobalGenerating', isGenerating); - return ( null, + drawerItemStyle: { display: 'none' }, }} /> From 7602327d4078675d6c9a226fbc9141692ef66ef6 Mon Sep 17 00:00:00 2001 From: pweglik Date: Tue, 17 Jun 2025 11:35:38 +0200 Subject: [PATCH 4/5] Fix --- apps/llm/app/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/llm/app/index.tsx b/apps/llm/app/index.tsx index 5beaa15ba..1753552f8 100644 --- a/apps/llm/app/index.tsx +++ b/apps/llm/app/index.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; import ColorPalette from '../colors'; import ExecutorchLogo from '../assets/icons/executorch.svg'; -export default function App() { +export default function Home() { const router = useRouter(); return ( @@ -13,19 +13,19 @@ export default function App() { router.navigate('app/llm/')} + onPress={() => router.navigate('llm/')} > LLM router.navigate('app/llm_tool_calling/')} + onPress={() => router.navigate('llm_tool_calling/')} > LLM Tool Calling router.navigate('app/voice_chat/')} + onPress={() => router.navigate('voice_chat/')} > Voice Chat From 9a86c70718992ab3f1604791fcf781e319e0b3cd Mon Sep 17 00:00:00 2001 From: mlodyjesienin Date: Tue, 17 Jun 2025 12:54:50 +0200 Subject: [PATCH 5/5] fix: small styling layout fixes --- apps/computer-vision/app/_layout.tsx | 27 +++++++++++++++++++++++++-- apps/llm/app/_layout.tsx | 27 +++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/apps/computer-vision/app/_layout.tsx b/apps/computer-vision/app/_layout.tsx index 2fa8c1e3a..35a0cc0da 100644 --- a/apps/computer-vision/app/_layout.tsx +++ b/apps/computer-vision/app/_layout.tsx @@ -1,7 +1,7 @@ import { Drawer } from 'expo-router/drawer'; import ColorPalette from '../colors'; import React, { useState } from 'react'; -import { Text } from 'react-native'; +import { Text, StyleSheet, View } from 'react-native'; import { DrawerContentComponentProps, @@ -21,7 +21,10 @@ function CustomDrawerContent(props: CustomDrawerProps) { {!isGenerating ? ( ) : ( - Model is generating. Interrupt before switching model + + Model is generating... + Interrupt before switching model + )} ); @@ -101,6 +104,7 @@ export default function _layout() { name="index" options={{ drawerLabel: () => null, + title: 'Main Menu', drawerItemStyle: { display: 'none' }, }} /> @@ -108,3 +112,22 @@ export default function _layout() { ); } + +const styles = StyleSheet.create({ + centerContent: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: 20, + }, + mainText: { + fontSize: 18, + fontWeight: 'bold', + marginBottom: 10, + color: ColorPalette.primary, + }, + subText: { + fontSize: 14, + color: ColorPalette.strongPrimary, + }, +}); diff --git a/apps/llm/app/_layout.tsx b/apps/llm/app/_layout.tsx index cc57b086d..786cb2de1 100644 --- a/apps/llm/app/_layout.tsx +++ b/apps/llm/app/_layout.tsx @@ -1,7 +1,7 @@ import { Drawer } from 'expo-router/drawer'; import ColorPalette from '../colors'; import React, { useState } from 'react'; -import { Text } from 'react-native'; +import { Text, StyleSheet, View } from 'react-native'; import { DrawerContentComponentProps, @@ -21,7 +21,10 @@ function CustomDrawerContent(props: CustomDrawerProps) { {!isGenerating ? ( ) : ( - Model is generating. Interrupt before switching model + + Model is generating... + Interrupt before switching model + )} ); @@ -77,6 +80,7 @@ export default function _layout() { name="index" options={{ drawerLabel: () => null, + title: 'Main Menu', drawerItemStyle: { display: 'none' }, }} /> @@ -84,3 +88,22 @@ export default function _layout() { ); } + +const styles = StyleSheet.create({ + centerContent: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: 20, + }, + mainText: { + fontSize: 18, + fontWeight: 'bold', + marginBottom: 10, + color: ColorPalette.primary, + }, + subText: { + fontSize: 14, + color: ColorPalette.strongPrimary, + }, +});