Skip to content

Commit 9807108

Browse files
committed
chore: add suggestions from code review
1 parent 623ede8 commit 9807108

11 files changed

Lines changed: 111 additions & 25 deletions

File tree

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import React, { useContext, useEffect, useState } from 'react';
1010
import { GeneratingContext } from '../../context';
1111
import ScreenWrapper from '../../ScreenWrapper';
1212
import { StatsBar } from '../../components/StatsBar';
13+
import ErrorBanner from '../../components/ErrorBanner';
1314

1415
export default function ClassificationScreen() {
1516
const [results, setResults] = useState<{ label: string; score: number }[]>(
@@ -18,12 +19,19 @@ export default function ClassificationScreen() {
1819
const [imageUri, setImageUri] = useState('');
1920
const [inferenceTime, setInferenceTime] = useState<number | null>(null);
2021

22+
const [error, setError] = useState<string | null>(null);
23+
2124
const model = useClassification({ model: EFFICIENTNET_V2_S_QUANTIZED });
2225
const { setGlobalGenerating } = useContext(GeneratingContext);
26+
2327
useEffect(() => {
2428
setGlobalGenerating(model.isGenerating);
2529
}, [model.isGenerating, setGlobalGenerating]);
2630

31+
useEffect(() => {
32+
if (model.error) setError(String(model.error));
33+
}, [model.error]);
34+
2735
const handleCameraPress = async (isCamera: boolean) => {
2836
const image = await getImage(isCamera);
2937
const uri = image?.uri;
@@ -46,21 +54,24 @@ export default function ClassificationScreen() {
4654
.map(([label, score]) => ({ label, score: score as number }));
4755
setResults(top10);
4856
} catch (e) {
49-
console.error(e);
57+
setError(e instanceof Error ? e.message : String(e));
5058
}
5159
}
5260
};
5361

54-
if (!model.isReady) {
62+
if (!model.isReady && !model.error) {
5563
return (
5664
<Spinner
57-
visible={!model.isReady}
65+
visible={true}
5866
textContent={`Loading the model ${(model.downloadProgress * 100).toFixed(0)} %`}
5967
/>
6068
);
6169
}
70+
6271
return (
6372
<ScreenWrapper>
73+
<ErrorBanner message={error} onDismiss={() => setError(null)} />
74+
6475
<View style={styles.imageContainer}>
6576
<Image
6677
style={styles.image}

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,34 @@ import React, { useContext, useEffect, useState } from 'react';
88
import { GeneratingContext } from '../../context';
99
import ScreenWrapper from '../../ScreenWrapper';
1010
import { StatsBar } from '../../components/StatsBar';
11+
import ErrorBanner from '../../components/ErrorBanner';
1112

12-
export default function VerticalOCRScree() {
13+
export default function VerticalOCRScreen() {
1314
const [imageUri, setImageUri] = useState('');
1415
const [results, setResults] = useState<any[]>([]);
1516
const [imageDimensions, setImageDimensions] = useState<{
1617
width: number;
1718
height: number;
1819
}>();
1920
const [inferenceTime, setInferenceTime] = useState<number | null>(null);
21+
22+
const [error, setError] = useState<string | null>(null);
23+
2024
const model = useVerticalOCR({
2125
model: OCR_ENGLISH,
2226
independentCharacters: true,
2327
});
28+
2429
const { setGlobalGenerating } = useContext(GeneratingContext);
30+
2531
useEffect(() => {
2632
setGlobalGenerating(model.isGenerating);
2733
}, [model.isGenerating, setGlobalGenerating]);
2834

35+
useEffect(() => {
36+
if (model.error) setError(String(model.error));
37+
}, [model.error]);
38+
2939
const handleCameraPress = async (isCamera: boolean) => {
3040
const image = await getImage(isCamera);
3141
const width = image?.width;
@@ -46,14 +56,14 @@ export default function VerticalOCRScree() {
4656
setInferenceTime(Date.now() - start);
4757
setResults(output);
4858
} catch (e) {
49-
console.error(e);
59+
setError(e instanceof Error ? e.message : String(e));
5060
}
5161
};
5262

53-
if (!model.isReady) {
63+
if (!model.isReady && !model.error) {
5464
return (
5565
<Spinner
56-
visible={!model.isReady}
66+
visible={true}
5767
textContent={`Loading the model ${(model.downloadProgress * 100).toFixed(0)} %`}
5868
/>
5969
);
@@ -62,6 +72,8 @@ export default function VerticalOCRScree() {
6272
return (
6373
<ScreenWrapper>
6474
<View style={styles.container}>
75+
<ErrorBanner message={error} onDismiss={() => setError(null)} />
76+
6577
<View style={styles.imageContainer}>
6678
{imageUri && imageDimensions?.width && imageDimensions?.height ? (
6779
<ImageWithBboxes2
@@ -87,7 +99,7 @@ export default function VerticalOCRScree() {
8799
{results.map(({ text, score }, index) => (
88100
<View key={index} style={styles.resultRecord}>
89101
<Text style={styles.resultLabel}>{text}</Text>
90-
<Text>{score.toFixed(3)}</Text>
102+
<Text>{score?.toFixed(3)}</Text>
91103
</View>
92104
))}
93105
</ScrollView>

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

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import SegmentationTask from '../../components/vision_camera/tasks/SegmentationT
3434
import InstanceSegmentationTask from '../../components/vision_camera/tasks/InstanceSegmentationTask';
3535
import OCRTask from '../../components/vision_camera/tasks/OCRTask';
3636
import StyleTransferTask from '../../components/vision_camera/tasks/StyleTransferTask';
37+
// 1. Import ErrorBanner
38+
import ErrorBanner from '../../components/ErrorBanner';
3739

3840
type TaskId =
3941
| 'classification'
@@ -112,8 +114,6 @@ const TASKS: Task[] = [
112114
},
113115
];
114116

115-
// Module-level consts so worklets in task components can always reference the same stable objects.
116-
// Never replaced — only mutated via setBlocking to avoid closure staleness.
117117
const frameKillSwitch = createSynchronizable(false);
118118
const cameraPositionSync = createSynchronizable<'front' | 'back'>('back');
119119

@@ -132,6 +132,9 @@ export default function VisionCameraScreen() {
132132
const [frameOutput, setFrameOutput] = useState<ReturnType<
133133
typeof useFrameOutput
134134
> | null>(null);
135+
136+
const [error, setError] = useState<string | null>(null);
137+
135138
const { setGlobalGenerating } = useContext(GeneratingContext);
136139

137140
const isFocused = useIsFocused();
@@ -150,6 +153,7 @@ export default function VisionCameraScreen() {
150153

151154
useEffect(() => {
152155
frameKillSwitch.setBlocking(true);
156+
setError(null);
153157
const id = setTimeout(() => {
154158
frameKillSwitch.setBlocking(false);
155159
}, 300);
@@ -172,6 +176,10 @@ export default function VisionCameraScreen() {
172176
[setGlobalGenerating]
173177
);
174178

179+
const handleErrorChange = useCallback((errorMessage: string | null) => {
180+
setError(errorMessage);
181+
}, []);
182+
175183
if (!cameraPermission.hasPermission) {
176184
return (
177185
<View style={styles.centered}>
@@ -209,12 +217,17 @@ export default function VisionCameraScreen() {
209217
onProgressChange: setDownloadProgress,
210218
onGeneratingChange: handleGeneratingChange,
211219
onFpsChange: handleFpsChange,
220+
onErrorChange: handleErrorChange,
212221
};
213222

214223
return (
215224
<View style={styles.container}>
216225
<StatusBar barStyle="light-content" translucent />
217226

227+
<View style={[styles.errorOverlay, { paddingTop: insets.top }]}>
228+
<ErrorBanner message={error} onDismiss={() => setError(null)} />
229+
</View>
230+
218231
<Camera
219232
style={StyleSheet.absoluteFill}
220233
device={device}
@@ -224,7 +237,6 @@ export default function VisionCameraScreen() {
224237
orientationSource="device"
225238
/>
226239

227-
{/* Layout sentinel — measures the full-screen area for bbox/canvas sizing */}
228240
<View
229241
style={StyleSheet.absoluteFill}
230242
pointerEvents="none"
@@ -280,7 +292,7 @@ export default function VisionCameraScreen() {
280292
/>
281293
)}
282294

283-
{!isReady && (
295+
{!isReady && !error && (
284296
<View style={styles.loadingOverlay}>
285297
<Spinner
286298
visible
@@ -387,6 +399,13 @@ export default function VisionCameraScreen() {
387399

388400
const styles = StyleSheet.create({
389401
container: { flex: 1, backgroundColor: 'black' },
402+
errorOverlay: {
403+
position: 'absolute',
404+
top: 0,
405+
left: 0,
406+
right: 0,
407+
zIndex: 100,
408+
},
390409
centered: {
391410
flex: 1,
392411
backgroundColor: 'black',
@@ -407,6 +426,7 @@ const styles = StyleSheet.create({
407426
backgroundColor: 'rgba(0,0,0,0.6)',
408427
justifyContent: 'center',
409428
alignItems: 'center',
429+
zIndex: 10,
410430
},
411431
topOverlay: {
412432
position: 'absolute',
@@ -415,6 +435,7 @@ const styles = StyleSheet.create({
415435
right: 0,
416436
alignItems: 'center',
417437
gap: 8,
438+
zIndex: 5,
418439
},
419440
titleRow: {
420441
alignItems: 'center',
@@ -487,6 +508,7 @@ const styles = StyleSheet.create({
487508
left: 0,
488509
right: 0,
489510
alignItems: 'center',
511+
zIndex: 5,
490512
},
491513
flipButton: {
492514
width: 56,

apps/computer-vision/components/vision_camera/tasks/ClassificationTask.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,16 @@ export default function ClassificationTask({
1717
onProgressChange,
1818
onGeneratingChange,
1919
onFpsChange,
20+
onErrorChange,
2021
}: Props) {
2122
const model = useClassification({ model: EFFICIENTNET_V2_S });
2223
const [classResult, setClassResult] = useState({ label: '', score: 0 });
2324
const lastFrameTimeRef = useRef(Date.now());
2425

26+
useEffect(() => {
27+
onErrorChange(model.error ? String(model.error) : null);
28+
}, [model.error, onErrorChange]);
29+
2530
useEffect(() => {
2631
onReadyChange(model.isReady);
2732
}, [model.isReady, onReadyChange]);

apps/computer-vision/components/vision_camera/tasks/InstanceSegmentationTask.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default function InstanceSegmentationTask({
3434
onProgressChange,
3535
onGeneratingChange,
3636
onFpsChange,
37+
onErrorChange,
3738
}: Props) {
3839
const yolo26n = useInstanceSegmentation({
3940
model: YOLO26N_SEG,
@@ -51,6 +52,10 @@ export default function InstanceSegmentationTask({
5152
const [imageSize, setImageSize] = useState({ width: 1, height: 1 });
5253
const lastFrameTimeRef = useRef(Date.now());
5354

55+
useEffect(() => {
56+
onErrorChange(active.error ? String(active.error) : null);
57+
}, [active.error, onErrorChange]);
58+
5459
useEffect(() => {
5560
onReadyChange(active.isReady);
5661
}, [active.isReady, onReadyChange]);

apps/computer-vision/components/vision_camera/tasks/OCRTask.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@ export default function OCRTask({
1717
onProgressChange,
1818
onGeneratingChange,
1919
onFpsChange,
20+
onErrorChange,
2021
}: Props) {
2122
const model = useOCR({ model: OCR_ENGLISH });
2223
const [detections, setDetections] = useState<OCRDetection[]>([]);
2324
const [imageSize, setImageSize] = useState({ width: 1, height: 1 });
2425
const lastFrameTimeRef = useRef(Date.now());
2526

27+
useEffect(() => {
28+
onErrorChange(model.error ? String(model.error) : null);
29+
}, [model.error, onErrorChange]);
30+
2631
useEffect(() => {
2732
onReadyChange(model.isReady);
2833
}, [model.isReady, onReadyChange]);

apps/computer-vision/components/vision_camera/tasks/ObjectDetectionTask.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default function ObjectDetectionTask({
2525
onProgressChange,
2626
onGeneratingChange,
2727
onFpsChange,
28+
onErrorChange,
2829
}: Props) {
2930
const ssdlite = useObjectDetection({
3031
model: SSDLITE_320_MOBILENET_V3_LARGE,
@@ -41,6 +42,10 @@ export default function ObjectDetectionTask({
4142
const [imageSize, setImageSize] = useState({ width: 1, height: 1 });
4243
const lastFrameTimeRef = useRef(Date.now());
4344

45+
useEffect(() => {
46+
onErrorChange(active.error ? String(active.error) : null);
47+
}, [active.error, onErrorChange]);
48+
4449
useEffect(() => {
4550
onReadyChange(active.isReady);
4651
}, [active.isReady, onReadyChange]);

apps/computer-vision/components/vision_camera/tasks/SegmentationTask.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export default function SegmentationTask({
4444
onProgressChange,
4545
onGeneratingChange,
4646
onFpsChange,
47+
onErrorChange,
4748
}: Props) {
4849
const segDeeplabResnet50 = useSemanticSegmentation({
4950
model: DEEPLAB_V3_RESNET50_QUANTIZED,
@@ -87,6 +88,10 @@ export default function SegmentationTask({
8788
const [maskImage, setMaskImage] = useState<SkImage | null>(null);
8889
const lastFrameTimeRef = useRef(Date.now());
8990

91+
useEffect(() => {
92+
onErrorChange(active.error ? String(active.error) : null);
93+
}, [active.error, onErrorChange]);
94+
9095
useEffect(() => {
9196
onReadyChange(active.isReady);
9297
}, [active.isReady, onReadyChange]);

apps/computer-vision/components/vision_camera/tasks/StyleTransferTask.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export default function StyleTransferTask({
3131
onProgressChange,
3232
onGeneratingChange,
3333
onFpsChange,
34+
onErrorChange,
3435
}: Props) {
3536
const candy = useStyleTransfer({
3637
model: STYLE_TRANSFER_CANDY,
@@ -46,6 +47,10 @@ export default function StyleTransferTask({
4647
const [styledImage, setStyledImage] = useState<SkImage | null>(null);
4748
const lastFrameTimeRef = useRef(Date.now());
4849

50+
useEffect(() => {
51+
onErrorChange(active.error ? String(active.error) : null);
52+
}, [active.error, onErrorChange]);
53+
4954
useEffect(() => {
5055
onReadyChange(active.isReady);
5156
}, [active.isReady, onReadyChange]);

apps/computer-vision/components/vision_camera/tasks/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export type TaskProps = {
1111
onProgressChange: (progress: number) => void;
1212
onGeneratingChange: (isGenerating: boolean) => void;
1313
onFpsChange: (fps: number, frameMs: number) => void;
14+
onErrorChange: (error: string | null) => void;
1415
};

0 commit comments

Comments
 (0)