Skip to content

Commit ab43cd8

Browse files
committed
chore: add unified error handling banner in all demo apps
1 parent f5014fa commit ab43cd8

16 files changed

Lines changed: 342 additions & 66 deletions

File tree

apps/computer-vision/app/object_detection/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import ImageWithBboxes from '../../components/ImageWithBboxes';
1111
import React, { useContext, useEffect, useState } from 'react';
1212
import { GeneratingContext } from '../../context';
1313
import ScreenWrapper from '../../ScreenWrapper';
14+
import ErrorBanner from '../../components/ErrorBanner';
1415

1516
export default function ObjectDetectionScreen() {
1617
const [imageUri, setImageUri] = useState('');
1718
const [results, setResults] = useState<Detection[]>([]);
19+
const [error, setError] = useState<string | null>(null);
1820
const [imageDimensions, setImageDimensions] = useState<{
1921
width: number;
2022
height: number;
@@ -26,6 +28,10 @@ export default function ObjectDetectionScreen() {
2628
setGlobalGenerating(rfDetr.isGenerating);
2729
}, [rfDetr.isGenerating, setGlobalGenerating]);
2830

31+
useEffect(() => {
32+
if (rfDetr.error) setError(String(rfDetr.error));
33+
}, [rfDetr.error]);
34+
2935
const handleCameraPress = async (isCamera: boolean) => {
3036
const image = await getImage(isCamera);
3137
const uri = image?.uri;
@@ -45,7 +51,7 @@ export default function ObjectDetectionScreen() {
4551
const output = await rfDetr.forward(imageUri);
4652
setResults(output);
4753
} catch (e) {
48-
console.error(e);
54+
setError(e instanceof Error ? e.message : String(e));
4955
}
5056
}
5157
};
@@ -61,6 +67,7 @@ export default function ObjectDetectionScreen() {
6167

6268
return (
6369
<ScreenWrapper>
70+
<ErrorBanner message={error} onDismiss={() => setError(null)} />
6471
<View style={styles.imageContainer}>
6572
<View style={styles.image}>
6673
{imageUri && imageDimensions?.width && imageDimensions?.height ? (

apps/computer-vision/app/ocr/index.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import ImageWithBboxes2 from '../../components/ImageWithOCRBboxes';
77
import React, { useContext, useEffect, useState } from 'react';
88
import { GeneratingContext } from '../../context';
99
import ScreenWrapper from '../../ScreenWrapper';
10+
import ErrorBanner from '../../components/ErrorBanner';
1011

1112
export default function OCRScreen() {
1213
const [imageUri, setImageUri] = useState('');
1314
const [results, setResults] = useState<any[]>([]);
15+
const [error, setError] = useState<string | null>(null);
1416
const [imageDimensions, setImageDimensions] = useState<{
1517
width: number;
1618
height: number;
@@ -24,6 +26,10 @@ export default function OCRScreen() {
2426
setGlobalGenerating(model.isGenerating);
2527
}, [model.isGenerating, setGlobalGenerating]);
2628

29+
useEffect(() => {
30+
if (model.error) setError(String(model.error));
31+
}, [model.error]);
32+
2733
const handleCameraPress = async (isCamera: boolean) => {
2834
const image = await getImage(isCamera);
2935
const width = image?.width;
@@ -41,21 +47,22 @@ export default function OCRScreen() {
4147
const output = await model.forward(imageUri);
4248
setResults(output);
4349
} catch (e) {
44-
console.error(e);
50+
setError(e instanceof Error ? e.message : String(e));
4551
}
4652
};
4753

48-
if (!model.isReady) {
54+
if (!model.isReady && !model.error) {
4955
return (
5056
<Spinner
51-
visible={!model.isReady}
57+
visible={true}
5258
textContent={`Loading the model ${(model.downloadProgress * 100).toFixed(0)} %`}
5359
/>
5460
);
5561
}
5662

5763
return (
5864
<ScreenWrapper>
65+
<ErrorBanner message={error} onDismiss={() => setError(null)} />
5966
<View style={styles.container}>
6067
<View style={styles.imageContainer}>
6168
{imageUri && imageDimensions?.width && imageDimensions?.height ? (

apps/computer-vision/app/semantic_segmentation/index.tsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { View, StyleSheet, Image } from 'react-native';
1717
import React, { useContext, useEffect, useState } from 'react';
1818
import { GeneratingContext } from '../../context';
1919
import ScreenWrapper from '../../ScreenWrapper';
20+
import ErrorBanner from '../../components/ErrorBanner';
2021

2122
const numberToColor: number[][] = [
2223
[255, 87, 51], // 0 Red
@@ -44,19 +45,29 @@ const numberToColor: number[][] = [
4445

4546
export default function SemanticSegmentationScreen() {
4647
const { setGlobalGenerating } = useContext(GeneratingContext);
47-
const { isReady, isGenerating, downloadProgress, forward } =
48-
useSemanticSegmentation({
49-
model: DEEPLAB_V3_MOBILENET_V3_LARGE_QUANTIZED,
50-
});
48+
const {
49+
isReady,
50+
isGenerating,
51+
downloadProgress,
52+
forward,
53+
error: modelError,
54+
} = useSemanticSegmentation({
55+
model: DEEPLAB_V3_MOBILENET_V3_LARGE_QUANTIZED,
56+
});
5157
const [imageUri, setImageUri] = useState('');
5258
const [imageSize, setImageSize] = useState({ width: 0, height: 0 });
5359
const [segImage, setSegImage] = useState<SkImage | null>(null);
5460
const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
61+
const [error, setError] = useState<string | null>(null);
5562

5663
useEffect(() => {
5764
setGlobalGenerating(isGenerating);
5865
}, [isGenerating, setGlobalGenerating]);
5966

67+
useEffect(() => {
68+
if (modelError) setError(String(modelError));
69+
}, [modelError]);
70+
6071
const handleCameraPress = async (isCamera: boolean) => {
6172
const image = await getImage(isCamera);
6273
if (!image?.uri) return;
@@ -100,21 +111,22 @@ export default function SemanticSegmentationScreen() {
100111
);
101112
setSegImage(img);
102113
} catch (e) {
103-
console.error(e);
114+
setError(e instanceof Error ? e.message : String(e));
104115
}
105116
};
106117

107-
if (!isReady) {
118+
if (!isReady && !modelError) {
108119
return (
109120
<Spinner
110-
visible={!isReady}
121+
visible={true}
111122
textContent={`Loading the model ${(downloadProgress * 100).toFixed(0)} %`}
112123
/>
113124
);
114125
}
115126

116127
return (
117128
<ScreenWrapper>
129+
<ErrorBanner message={error} onDismiss={() => setError(null)} />
118130
<View style={styles.imageCanvasContainer}>
119131
<View style={styles.imageContainer}>
120132
<Image

apps/computer-vision/app/style_transfer/index.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { View, StyleSheet, Image } from 'react-native';
99
import React, { useContext, useEffect, useState } from 'react';
1010
import { GeneratingContext } from '../../context';
1111
import ScreenWrapper from '../../ScreenWrapper';
12+
import ErrorBanner from '../../components/ErrorBanner';
1213

1314
export default function StyleTransferScreen() {
1415
const model = useStyleTransfer({ model: STYLE_TRANSFER_CANDY_QUANTIZED });
@@ -17,8 +18,13 @@ export default function StyleTransferScreen() {
1718
setGlobalGenerating(model.isGenerating);
1819
}, [model.isGenerating, setGlobalGenerating]);
1920

21+
useEffect(() => {
22+
if (model.error) setError(String(model.error));
23+
}, [model.error]);
24+
2025
const [imageUri, setImageUri] = useState('');
2126
const [styledUri, setStyledUri] = useState('');
27+
const [error, setError] = useState<string | null>(null);
2228

2329
const handleCameraPress = async (isCamera: boolean) => {
2430
const image = await getImage(isCamera);
@@ -35,22 +41,23 @@ export default function StyleTransferScreen() {
3541
const uri = await model.forward(imageUri, 'url');
3642
setStyledUri(uri);
3743
} catch (e) {
38-
console.error(e);
44+
setError(e instanceof Error ? e.message : String(e));
3945
}
4046
}
4147
};
4248

43-
if (!model.isReady) {
49+
if (!model.isReady && !model.error) {
4450
return (
4551
<Spinner
46-
visible={!model.isReady}
52+
visible={true}
4753
textContent={`Loading the model ${(model.downloadProgress * 100).toFixed(0)} %`}
4854
/>
4955
);
5056
}
5157

5258
return (
5359
<ScreenWrapper>
60+
<ErrorBanner message={error} onDismiss={() => setError(null)} />
5461
<View style={styles.imageContainer}>
5562
<Image
5663
style={styles.image}

apps/computer-vision/app/text_to_image/index.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { GeneratingContext } from '../../context';
1313
import ColorPalette from '../../colors';
1414
import ProgressBar from '../../components/ProgressBar';
1515
import { BottomBarWithTextInput } from '../../components/BottomBarWithTextInput';
16+
import ErrorBanner from '../../components/ErrorBanner';
1617

1718
export default function TextToImageScreen() {
1819
const [inferenceStepIdx, setInferenceStepIdx] = useState<number>(0);
@@ -21,6 +22,7 @@ export default function TextToImageScreen() {
2122
const [steps, setSteps] = useState<number>(40);
2223
const [showTextInput, setShowTextInput] = useState(false);
2324
const [keyboardVisible, setKeyboardVisible] = useState(false);
25+
const [error, setError] = useState<string | null>(null);
2426

2527
const imageSize = 224;
2628
const model = useTextToImage({
@@ -34,6 +36,10 @@ export default function TextToImageScreen() {
3436
setGlobalGenerating(model.isGenerating);
3537
}, [model.isGenerating, setGlobalGenerating]);
3638

39+
useEffect(() => {
40+
if (model.error) setError(String(model.error));
41+
}, [model.error]);
42+
3743
useEffect(() => {
3844
const showSub = Keyboard.addListener('keyboardDidShow', () => {
3945
setKeyboardVisible(true);
@@ -60,18 +66,18 @@ export default function TextToImageScreen() {
6066
}
6167
setImage(output);
6268
} catch (e) {
63-
console.error(e);
69+
setError(e instanceof Error ? e.message : String(e));
6470
setImageTitle(null);
6571
} finally {
6672
setInferenceStepIdx(0);
6773
}
6874
};
6975

70-
if (!model.isReady) {
76+
if (!model.isReady && !model.error) {
7177
// TODO: Update when #614 merged
7278
return (
7379
<Spinner
74-
visible={!model.isReady}
80+
visible={true}
7581
textContent={`Loading the model ${(model.downloadProgress * 100).toFixed(0)} %`}
7682
/>
7783
);
@@ -87,6 +93,7 @@ export default function TextToImageScreen() {
8793
<View style={styles.container}>
8894
{keyboardVisible && <View style={styles.overlay} />}
8995

96+
<ErrorBanner message={error} onDismiss={() => setError(null)} />
9097
<View style={styles.titleContainer}>
9198
{imageTitle && <Text style={styles.titleText}>{imageTitle}</Text>}
9299
</View>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react';
2+
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
3+
4+
interface ErrorBannerProps {
5+
message: string | null;
6+
onDismiss: () => void;
7+
}
8+
9+
export default function ErrorBanner({ message, onDismiss }: ErrorBannerProps) {
10+
if (!message) return null;
11+
12+
return (
13+
<View style={styles.container}>
14+
<Text style={styles.message} numberOfLines={3}>
15+
{message}
16+
</Text>
17+
<TouchableOpacity onPress={onDismiss} style={styles.closeButton}>
18+
<Text style={styles.closeText}></Text>
19+
</TouchableOpacity>
20+
</View>
21+
);
22+
}
23+
24+
const styles = StyleSheet.create({
25+
container: {
26+
backgroundColor: '#FEE2E2',
27+
borderLeftWidth: 4,
28+
borderLeftColor: '#EF4444',
29+
borderRadius: 8,
30+
marginHorizontal: 16,
31+
marginVertical: 8,
32+
paddingVertical: 10,
33+
paddingLeft: 12,
34+
paddingRight: 8,
35+
flexDirection: 'row',
36+
alignItems: 'center',
37+
},
38+
message: {
39+
flex: 1,
40+
color: '#991B1B',
41+
fontSize: 14,
42+
lineHeight: 20,
43+
},
44+
closeButton: {
45+
padding: 4,
46+
marginLeft: 8,
47+
},
48+
closeText: {
49+
color: '#991B1B',
50+
fontSize: 16,
51+
fontWeight: '600',
52+
},
53+
});

apps/llm/app/llm/index.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Messages from '../../components/Messages';
1818
import { useIsFocused } from '@react-navigation/native';
1919
import { GeneratingContext } from '../../context';
2020
import Spinner from '../../components/Spinner';
21+
import ErrorBanner from '../../components/ErrorBanner';
2122

2223
export default function LLMScreenWrapper() {
2324
const isFocused = useIsFocused();
@@ -31,11 +32,10 @@ function LLMScreen() {
3132
const { setGlobalGenerating } = useContext(GeneratingContext);
3233

3334
const llm = useLLM({ model: LLAMA3_2_1B_SPINQUANT });
35+
const [error, setError] = useState<string | null>(null);
3436

3537
useEffect(() => {
36-
if (llm.error) {
37-
console.error('LLM error:', llm.error);
38-
}
38+
if (llm.error) setError(String(llm.error));
3939
}, [llm.error]);
4040

4141
useEffect(() => {
@@ -48,13 +48,13 @@ function LLMScreen() {
4848
try {
4949
await llm.sendMessage(userInput);
5050
} catch (e) {
51-
console.error(e);
51+
setError(e instanceof Error ? e.message : String(e));
5252
}
5353
};
5454

55-
return !llm.isReady ? (
55+
return !llm.isReady && !llm.error ? (
5656
<Spinner
57-
visible={!llm.isReady}
57+
visible={true}
5858
textContent={`Loading the model ${(llm.downloadProgress * 100).toFixed(0)} %`}
5959
/>
6060
) : (
@@ -68,6 +68,7 @@ function LLMScreen() {
6868
keyboardVerticalOffset={Platform.OS === 'ios' ? 120 : 40}
6969
>
7070
<View style={styles.container}>
71+
<ErrorBanner message={error} onDismiss={() => setError(null)} />
7172
{llm.messageHistory.length ? (
7273
<View style={styles.chatContainer}>
7374
<Messages

0 commit comments

Comments
 (0)