diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..88b4bcd
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,3 @@
+API_KEY=Put Open AI key here
+GPT_MODEL=gpt-4o-mini
+#GPT_MODEL=gpt-4-turbo # More expensive model, use sparingly
\ No newline at end of file
diff --git a/README.md b/README.md
index 4ca5146..6042755 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,23 @@ This applications uses the React Native + Expo framework and by extension is pri
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
+### Setting Up Environment Variables
+
+To set up your environment variables:
+
+1. Copy the example environment variable file to create your own .env file:
+
+cp .env.example .env
+
+2. Open the .env file and add your OpenAI API key:
+
+API_KEY=your_openai_api_key_here
+MODEL_NAME=your_model_name
+
+3. Save the .env file.
+
+This setup allows you to run the application with your own API credentials, and you can switch models if needed.
+
### VSCode plugins
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
diff --git a/babel.config.js b/babel.config.js
index e11269c..c0fa271 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,9 +1,5 @@
module.exports = {
- presets: [
- "module:metro-react-native-babel-preset",
- "babel-preset-expo",
- "@babel/preset-typescript",
- ],
+ presets: ["babel-preset-expo", "@babel/preset-typescript"],
plugins: [
[
"module:react-native-dotenv",
@@ -14,5 +10,6 @@ module.exports = {
allowUndefined: false,
},
],
+ "react-native-reanimated/plugin",
],
};
diff --git a/components/ChipDetection.tsx b/components/ChipDetection.tsx
index dac07c9..78815ec 100644
--- a/components/ChipDetection.tsx
+++ b/components/ChipDetection.tsx
@@ -8,7 +8,7 @@ import {
ScrollView,
} from "react-native";
import * as ImagePicker from "expo-image-picker";
-import { API_KEY } from "@env";
+import { API_KEY, MODEL_NAME } from "@env";
const ChipDetection = ({ updateChipCount }) => {
const [imageUri, setImageUri] = useState(null);
@@ -16,7 +16,6 @@ const ChipDetection = ({ updateChipCount }) => {
const [error, setError] = useState(null);
const [lastDetectedChips, setLastDetectedChips] = useState({});
- // Ensure early return does not break hooks
const requestCameraPermissions = async () => {
const cameraPermission = await ImagePicker.requestCameraPermissionsAsync();
return cameraPermission.granted;
@@ -67,7 +66,7 @@ const ChipDetection = ({ updateChipCount }) => {
"Content-Type": "application/json",
},
body: JSON.stringify({
- model: "gpt-4o-mini",
+ model: MODEL_NAME,
messages: [
{
role: "system",
@@ -112,11 +111,10 @@ const ChipDetection = ({ updateChipCount }) => {
setLastDetectedChips(filteredData); // Store detected chip counts
updateChipCount(filteredData);
} catch (error) {
- console.error("Error processing image:", error);
setError("Failed to analyze the image.");
+ } finally {
+ setLoading(false);
}
-
- setLoading(false);
};
return (
diff --git a/components/__tests__/ChipDetection.test.tsx b/components/__tests__/ChipDetection.test.tsx
index dd1d010..cf42513 100644
--- a/components/__tests__/ChipDetection.test.tsx
+++ b/components/__tests__/ChipDetection.test.tsx
@@ -1,18 +1,150 @@
-import ChipDetection from "@/components/ChipDetection";
-import { render } from "@testing-library/react-native";
import React from "react";
+import { render, fireEvent, waitFor } from "@testing-library/react-native";
+import ChipDetection from "@/components/ChipDetection";
+import * as ImagePicker from "expo-image-picker";
-const totalChipsCount: number[] = [];
-const mockSetTotalChipsCount = jest.fn();
+const mockUpdateChipCount = jest.fn();
-const rend = render(
-
-);
+jest.mock("expo-image-picker", () => ({
+ requestCameraPermissionsAsync: jest.fn(),
+ launchImageLibraryAsync: jest.fn(),
+ launchCameraAsync: jest.fn(),
+ MediaTypeOptions: {
+ Images: "image",
+ },
+}));
-describe("tests for ChipDetection", () => {
- it.todo("first test");
- it.todo("second test");
+describe("ChipDetection", () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ global.fetch = jest.fn(() =>
+ Promise.resolve({
+ ok: true,
+ json: () =>
+ Promise.resolve({
+ choices: [
+ {
+ message: {
+ content: JSON.stringify({ red: 5, green: 3, blue: 0 }),
+ },
+ },
+ ],
+ }),
+ })
+ );
+ });
+
+ it("renders correctly", () => {
+ const { getByText } = render(
+
+ );
+ expect(getByText("Pick an Image")).toBeTruthy();
+ expect(getByText("Take a Photo")).toBeTruthy();
+ });
+
+ it("picks an image from the library", async () => {
+ ImagePicker.launchImageLibraryAsync.mockResolvedValueOnce({
+ canceled: false,
+ assets: [{ uri: "test-uri", base64: "test-base64" }],
+ });
+
+ const { getByText } = render(
+
+ );
+ fireEvent.press(getByText("Pick an Image"));
+
+ await waitFor(() => expect(mockUpdateChipCount).toHaveBeenCalled());
+ });
+
+ it("takes a photo with the camera", async () => {
+ ImagePicker.requestCameraPermissionsAsync.mockResolvedValueOnce({
+ granted: true,
+ });
+ ImagePicker.launchCameraAsync.mockResolvedValueOnce({
+ canceled: false,
+ assets: [{ uri: "test-camera-uri", base64: "test-camera-base64" }],
+ });
+
+ const { getByText } = render(
+
+ );
+ fireEvent.press(getByText("Take a Photo"));
+
+ await waitFor(() => expect(mockUpdateChipCount).toHaveBeenCalled());
+ });
+
+ it("handles camera permission denied", async () => {
+ ImagePicker.requestCameraPermissionsAsync.mockResolvedValueOnce({
+ granted: false,
+ });
+
+ const { getByText } = render(
+
+ );
+ fireEvent.press(getByText("Take a Photo"));
+
+ await waitFor(() =>
+ expect(
+ getByText("Camera permission is required to take a photo.")
+ ).toBeTruthy()
+ );
+ });
+
+ it("displays error message on image processing failure", async () => {
+ ImagePicker.launchImageLibraryAsync.mockResolvedValueOnce({
+ canceled: false,
+ assets: [{ uri: "test-uri", base64: "test-base64" }],
+ });
+
+ global.fetch.mockImplementationOnce(() =>
+ Promise.resolve({
+ ok: false,
+ json: () => Promise.resolve({ choices: [] }),
+ })
+ );
+
+ const { getByText } = render(
+
+ );
+ fireEvent.press(getByText("Pick an Image"));
+
+ await waitFor(() =>
+ expect(getByText("Failed to analyze the image.")).toBeTruthy()
+ );
+ });
+
+ it("handles valid API response correctly", async () => {
+ ImagePicker.launchImageLibraryAsync.mockResolvedValueOnce({
+ canceled: false,
+ assets: [{ uri: "test-uri", base64: "test-base64" }],
+ });
+
+ global.fetch.mockImplementationOnce(() =>
+ Promise.resolve({
+ ok: true,
+ json: () =>
+ Promise.resolve({
+ choices: [
+ {
+ message: {
+ content: JSON.stringify({ red: 5, green: 3 }),
+ },
+ },
+ ],
+ }),
+ })
+ );
+
+ const { getByText } = render(
+
+ );
+ fireEvent.press(getByText("Pick an Image"));
+
+ await waitFor(() =>
+ expect(mockUpdateChipCount).toHaveBeenCalledWith({
+ red: 5,
+ green: 3,
+ })
+ );
+ });
});
diff --git a/package-lock.json b/package-lock.json
index 852170d..46b6d12 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,8 +12,6 @@
"@expo/vector-icons": "^14.0.2",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14",
- "@tensorflow/tfjs": "^4.22.0",
- "@tensorflow/tfjs-react-native": "^1.0.0",
"expo": "~52.0.31",
"expo-blur": "~14.0.3",
"expo-constants": "~17.0.5",
@@ -54,8 +52,9 @@
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-native": "^5.0.0",
- "jest": "^29.2.1",
+ "jest": "^29.7.0",
"jest-expo": "~52.0.3",
+ "jest-fetch-mock": "^3.0.3",
"prettier": "^3.4.2",
"react-test-renderer": "18.3.1",
"typescript": "^5.7.3"
@@ -3948,19 +3947,6 @@
"react": "^16.8 || ^17.0 || ^18.0"
}
},
- "node_modules/@react-native-async-storage/async-storage": {
- "version": "1.24.0",
- "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz",
- "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "merge-options": "^3.0.4"
- },
- "peerDependencies": {
- "react-native": "^0.0.0-0 || >=0.60 <1.0"
- }
- },
"node_modules/@react-native/assets-registry": {
"version": "0.76.7",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.7.tgz",
@@ -4585,301 +4571,6 @@
"@sinonjs/commons": "^3.0.0"
}
},
- "node_modules/@tensorflow/tfjs": {
- "version": "4.22.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.22.0.tgz",
- "integrity": "sha512-0TrIrXs6/b7FLhLVNmfh8Sah6JgjBPH4mZ8JGb7NU6WW+cx00qK5BcAZxw7NCzxj6N8MRAIfHq+oNbPUNG5VAg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@tensorflow/tfjs-backend-cpu": "4.22.0",
- "@tensorflow/tfjs-backend-webgl": "4.22.0",
- "@tensorflow/tfjs-converter": "4.22.0",
- "@tensorflow/tfjs-core": "4.22.0",
- "@tensorflow/tfjs-data": "4.22.0",
- "@tensorflow/tfjs-layers": "4.22.0",
- "argparse": "^1.0.10",
- "chalk": "^4.1.0",
- "core-js": "3.29.1",
- "regenerator-runtime": "^0.13.5",
- "yargs": "^16.0.3"
- },
- "bin": {
- "tfjs-custom-module": "dist/tools/custom_module/cli.js"
- }
- },
- "node_modules/@tensorflow/tfjs-backend-cpu": {
- "version": "4.22.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.22.0.tgz",
- "integrity": "sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@types/seedrandom": "^2.4.28",
- "seedrandom": "^3.0.5"
- },
- "engines": {
- "yarn": ">= 1.3.2"
- },
- "peerDependencies": {
- "@tensorflow/tfjs-core": "4.22.0"
- }
- },
- "node_modules/@tensorflow/tfjs-backend-webgl": {
- "version": "4.22.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.22.0.tgz",
- "integrity": "sha512-H535XtZWnWgNwSzv538czjVlbJebDl5QTMOth4RXr2p/kJ1qSIXE0vZvEtO+5EC9b00SvhplECny2yDewQb/Yg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@tensorflow/tfjs-backend-cpu": "4.22.0",
- "@types/offscreencanvas": "~2019.3.0",
- "@types/seedrandom": "^2.4.28",
- "seedrandom": "^3.0.5"
- },
- "engines": {
- "yarn": ">= 1.3.2"
- },
- "peerDependencies": {
- "@tensorflow/tfjs-core": "4.22.0"
- }
- },
- "node_modules/@tensorflow/tfjs-converter": {
- "version": "4.22.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz",
- "integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "@tensorflow/tfjs-core": "4.22.0"
- }
- },
- "node_modules/@tensorflow/tfjs-core": {
- "version": "4.22.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz",
- "integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@types/long": "^4.0.1",
- "@types/offscreencanvas": "~2019.7.0",
- "@types/seedrandom": "^2.4.28",
- "@webgpu/types": "0.1.38",
- "long": "4.0.0",
- "node-fetch": "~2.6.1",
- "seedrandom": "^3.0.5"
- },
- "engines": {
- "yarn": ">= 1.3.2"
- }
- },
- "node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": {
- "version": "2019.7.3",
- "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
- "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
- "license": "MIT"
- },
- "node_modules/@tensorflow/tfjs-core/node_modules/node-fetch": {
- "version": "2.6.13",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz",
- "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==",
- "license": "MIT",
- "dependencies": {
- "whatwg-url": "^5.0.0"
- },
- "engines": {
- "node": "4.x || >=6.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
- }
- },
- "node_modules/@tensorflow/tfjs-core/node_modules/tr46": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
- "license": "MIT"
- },
- "node_modules/@tensorflow/tfjs-core/node_modules/webidl-conversions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
- "license": "BSD-2-Clause"
- },
- "node_modules/@tensorflow/tfjs-core/node_modules/whatwg-url": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
- "license": "MIT",
- "dependencies": {
- "tr46": "~0.0.3",
- "webidl-conversions": "^3.0.0"
- }
- },
- "node_modules/@tensorflow/tfjs-data": {
- "version": "4.22.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.22.0.tgz",
- "integrity": "sha512-dYmF3LihQIGvtgJrt382hSRH4S0QuAp2w1hXJI2+kOaEqo5HnUPG0k5KA6va+S1yUhx7UBToUKCBHeLHFQRV4w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@types/node-fetch": "^2.1.2",
- "node-fetch": "~2.6.1",
- "string_decoder": "^1.3.0"
- },
- "peerDependencies": {
- "@tensorflow/tfjs-core": "4.22.0",
- "seedrandom": "^3.0.5"
- }
- },
- "node_modules/@tensorflow/tfjs-data/node_modules/node-fetch": {
- "version": "2.6.13",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz",
- "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==",
- "license": "MIT",
- "dependencies": {
- "whatwg-url": "^5.0.0"
- },
- "engines": {
- "node": "4.x || >=6.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
- }
- },
- "node_modules/@tensorflow/tfjs-data/node_modules/tr46": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
- "license": "MIT"
- },
- "node_modules/@tensorflow/tfjs-data/node_modules/webidl-conversions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
- "license": "BSD-2-Clause"
- },
- "node_modules/@tensorflow/tfjs-data/node_modules/whatwg-url": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
- "license": "MIT",
- "dependencies": {
- "tr46": "~0.0.3",
- "webidl-conversions": "^3.0.0"
- }
- },
- "node_modules/@tensorflow/tfjs-layers": {
- "version": "4.22.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.22.0.tgz",
- "integrity": "sha512-lybPj4ZNj9iIAPUj7a8ZW1hg8KQGfqWLlCZDi9eM/oNKCCAgchiyzx8OrYoWmRrB+AM6VNEeIT+2gZKg5ReihA==",
- "license": "Apache-2.0 AND MIT",
- "peerDependencies": {
- "@tensorflow/tfjs-core": "4.22.0"
- }
- },
- "node_modules/@tensorflow/tfjs-react-native": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-react-native/-/tfjs-react-native-1.0.0.tgz",
- "integrity": "sha512-YzzJeb6ZDtCNBRvwKKQwASfcd0zIP8DWkQJyOlQPzJS/XIMMK1Qxf6LVLDpNoOC4FGdFd4QoGD6AvmU9gSUrHg==",
- "license": "Apache-2.0",
- "dependencies": {
- "base64-js": "^1.3.0",
- "buffer": "^5.2.1",
- "jpeg-js": "^0.4.3"
- },
- "peerDependencies": {
- "@react-native-async-storage/async-storage": "^1.13.0",
- "@tensorflow/tfjs-backend-cpu": "^4.13.0",
- "@tensorflow/tfjs-backend-webgl": "^4.13.0",
- "@tensorflow/tfjs-core": "^4.13.0",
- "expo-camera": "^13.4.4",
- "expo-gl": "^13.0.1",
- "react": "*",
- "react-native": ">= 0.72.0",
- "react-native-fs": "^2.20.0"
- }
- },
- "node_modules/@tensorflow/tfjs/node_modules/cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
- "wrap-ansi": "^7.0.0"
- }
- },
- "node_modules/@tensorflow/tfjs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "license": "MIT"
- },
- "node_modules/@tensorflow/tfjs/node_modules/regenerator-runtime": {
- "version": "0.13.11",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
- "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
- "license": "MIT"
- },
- "node_modules/@tensorflow/tfjs/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@tensorflow/tfjs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@tensorflow/tfjs/node_modules/yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
- "license": "MIT",
- "dependencies": {
- "cliui": "^7.0.2",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.0",
- "y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@tensorflow/tfjs/node_modules/yargs-parser": {
- "version": "20.2.9",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
- "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
- "license": "ISC",
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/@testing-library/dom": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
@@ -5173,12 +4864,6 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"license": "MIT"
},
- "node_modules/@types/long": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
- "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
- "license": "MIT"
- },
"node_modules/@types/node": {
"version": "22.13.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz",
@@ -5188,31 +4873,6 @@
"undici-types": "~6.20.0"
}
},
- "node_modules/@types/node-fetch": {
- "version": "2.6.12",
- "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz",
- "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "form-data": "^4.0.0"
- }
- },
- "node_modules/@types/node-fetch/node_modules/form-data": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
- "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "es-set-tostringtag": "^2.1.0",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/@types/node-forge": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz",
@@ -5222,12 +4882,6 @@
"@types/node": "*"
}
},
- "node_modules/@types/offscreencanvas": {
- "version": "2019.3.0",
- "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz",
- "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==",
- "license": "MIT"
- },
"node_modules/@types/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
@@ -5256,12 +4910,6 @@
"@types/react": "^18"
}
},
- "node_modules/@types/seedrandom": {
- "version": "2.4.34",
- "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz",
- "integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==",
- "license": "MIT"
- },
"node_modules/@types/stack-utils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
@@ -5495,12 +5143,6 @@
"@xtuc/long": "4.2.2"
}
},
- "node_modules/@webgpu/types": {
- "version": "0.1.38",
- "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz",
- "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==",
- "license": "BSD-3-Clause"
- },
"node_modules/@xmldom/xmldom": {
"version": "0.7.13",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz",
@@ -6245,12 +5887,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
- "node_modules/base-64": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
- "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==",
- "peer": true
- },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -7046,17 +6682,6 @@
"node": ">=6.6.0"
}
},
- "node_modules/core-js": {
- "version": "3.29.1",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz",
- "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==",
- "hasInstallScript": true,
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/core-js"
- }
- },
"node_modules/core-js-compat": {
"version": "3.40.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz",
@@ -7855,6 +7480,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -8525,19 +8151,6 @@
"react-native": "*"
}
},
- "node_modules/expo-camera": {
- "version": "13.9.0",
- "resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-13.9.0.tgz",
- "integrity": "sha512-CaVEsBfgCXf7o0K8+POwoGCyas79FkNovyzzfkYn3pJ9D6H4HaGzpLf9DBVHPw7tHyPPSMzhNoFkiytqDYQsrw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "invariant": "^2.2.4"
- },
- "peerDependencies": {
- "expo": "*"
- }
- },
"node_modules/expo-constants": {
"version": "17.0.5",
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.0.5.tgz",
@@ -8578,19 +8191,6 @@
"react": "*"
}
},
- "node_modules/expo-gl": {
- "version": "13.6.0",
- "resolved": "https://registry.npmjs.org/expo-gl/-/expo-gl-13.6.0.tgz",
- "integrity": "sha512-hhRC2ZTDpc2YoElojutJTOYQsjSxX68lgL+2TSgWRrrvUXFeua1Ohz5DL/0iNCeoabs1earf/4qhxIdozkrhMA==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "invariant": "^2.2.4"
- },
- "peerDependencies": {
- "expo": "*"
- }
- },
"node_modules/expo-haptics": {
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/expo-haptics/-/expo-haptics-14.0.1.tgz",
@@ -10320,16 +9920,6 @@
"node": ">=8"
}
},
- "node_modules/is-plain-obj": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
- "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@@ -11109,6 +10699,17 @@
"license": "MIT",
"peer": true
},
+ "node_modules/jest-fetch-mock": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz",
+ "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-fetch": "^3.0.4",
+ "promise-polyfill": "^8.1.3"
+ }
+ },
"node_modules/jest-get-type": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
@@ -11645,12 +11246,6 @@
"integrity": "sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==",
"license": "MIT"
},
- "node_modules/jpeg-js": {
- "version": "0.4.4",
- "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
- "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
- "license": "BSD-3-Clause"
- },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -12302,12 +11897,6 @@
"node": ">=4"
}
},
- "node_modules/long": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
- "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
- "license": "Apache-2.0"
- },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -12424,19 +12013,6 @@
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"license": "MIT"
},
- "node_modules/merge-options": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
- "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "is-plain-obj": "^2.1.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -14182,6 +13758,13 @@
"asap": "~2.0.3"
}
},
+ "node_modules/promise-polyfill": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz",
+ "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -14537,26 +14120,6 @@
"@babel/runtime": "^7.20.6"
}
},
- "node_modules/react-native-fs": {
- "version": "2.20.0",
- "resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.20.0.tgz",
- "integrity": "sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "base-64": "^0.1.0",
- "utf8": "^3.0.0"
- },
- "peerDependencies": {
- "react-native": "*",
- "react-native-windows": "*"
- },
- "peerDependenciesMeta": {
- "react-native-windows": {
- "optional": true
- }
- }
- },
"node_modules/react-native-gesture-handler": {
"version": "2.20.2",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.20.2.tgz",
@@ -15308,12 +14871,6 @@
"url": "https://opencollective.com/webpack"
}
},
- "node_modules/seedrandom": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
- "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
- "license": "MIT"
- },
"node_modules/selfsigned": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz",
@@ -16009,15 +15566,6 @@
"node": ">=4"
}
},
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
"node_modules/string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
@@ -17197,13 +16745,6 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
- "node_modules/utf8": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz",
- "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==",
- "license": "MIT",
- "peer": true
- },
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
diff --git a/package.json b/package.json
index 7e1ef7f..b480359 100644
--- a/package.json
+++ b/package.json
@@ -19,8 +19,6 @@
"@expo/vector-icons": "^14.0.2",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14",
- "@tensorflow/tfjs": "^4.22.0",
- "@tensorflow/tfjs-react-native": "^1.0.0",
"expo": "~52.0.31",
"expo-blur": "~14.0.3",
"expo-constants": "~17.0.5",
@@ -61,8 +59,9 @@
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-native": "^5.0.0",
- "jest": "^29.2.1",
+ "jest": "^29.7.0",
"jest-expo": "~52.0.3",
+ "jest-fetch-mock": "^3.0.3",
"prettier": "^3.4.2",
"react-test-renderer": "18.3.1",
"typescript": "^5.7.3"