diff --git a/.agents/skills/README.md b/.agents/skills/README.md new file mode 100644 index 0000000000..116779084a --- /dev/null +++ b/.agents/skills/README.md @@ -0,0 +1,11 @@ +# Agent Skills Directory + +This directory contains specialized agent skills (recipes) to guide AI coding agents (like Antigravity or Claude Code) in extending the codebase cleanly and consistently. + +## Available Skills + +- [Core Guidelines](./core-guidelines/SKILL.md) — Symmetrical architecture, conventions, and model rules. +- [Add Native Extension](./add-native-extension/SKILL.md) — C++ operations and JSI bindings. +- [Add Task Pipeline](./add-task-pipeline/SKILL.md) — TypeScript task pipelines and React hooks. +- [Model Schema Validation](./model-schema-validation/SKILL.md) — SymbolicTensor schemas and validation. +- [Verify and Build](./verify-and-build/SKILL.md) — TypeScript typechecking, native rebuilding, and troubleshooting. diff --git a/.agents/skills/add-native-extension/SKILL.md b/.agents/skills/add-native-extension/SKILL.md new file mode 100644 index 0000000000..cbd751d0c1 --- /dev/null +++ b/.agents/skills/add-native-extension/SKILL.md @@ -0,0 +1,201 @@ +--- +name: add-native-extension +description: Use when adding a C++ extension, writing native JSI host functions, registering functions in C++ install maps, or compiling native code. +metadata: + id: add_native_extension + scope: cpp/extensions/*, src/extensions/* +--- + +# Skill: Add a Native C++ Extension & JSI Bindings + +Use this guide to add custom, performance-critical native operations in C++ and expose them to TypeScript via React Native JSI. + +--- + +## 🚦 Architectural Guidelines + +Before writing any C++ code, ensure you adhere to the following principles: + +1. **Amdahl's Law & Premature Optimization**: + * Evaluate what percentage of total inference/pipeline time the processing step occupies. If the preprocessing/postprocessing step takes `< 5%` of the total inference budget, write it in **pure TypeScript** to reduce codebase complexity and maintenance overhead. + +2. **Destination Tensors & Local Memory**: + * **Local Memory is Allowed**: You can allocate temporary native C++ memory (such as stack variables, `std::vector`s, or dynamic memory cleaned up before the function exits) for intermediate calculations. + * **Destination Tensors**: If the operation writes dense output, the destination tensor must be pre-allocated by the caller (in TypeScript) and passed as an argument (e.g., `sigmoid(src, dst)`). + * **Primitive Array Returns**: If the operation produces variable-sized non-dense outputs (like bounding box indices in Non-Maximum Suppression (NMS)), return a plain `jsi::Array` of primitives (like indices or coordinates). + * *Example*: `nms(boxes, scores, options)` returns a `jsi::Array` of indices (e.g., `[0, 4, 12]`) rather than a new tensor. This avoids all native memory management overhead for variable-sized outputs. + +## 🚫 Avoid / Anti-Patterns + +* **Do NOT return implicitly allocated JSI Tensors:** Never return newly created `TensorHostObject` instances from C++. This forces the JavaScript layer to reason about their garbage collection and manual lifetimes, leading to native memory leaks. +* **Do NOT define default parameters in C++:** Native C++ functions must never define default argument values (e.g. `axis = -1`). Define all default values explicitly in the TypeScript wrapper layer instead. +* **Do NOT perform in-place mutation without safety checks:** Never allow inputs and outputs to share the same underlying instance. + +--- + +--- + +## 🛠️ Step-by-Step Implementation + +### Step 1: Create the Native Operation Files +Under `cpp/extensions//`, create or modify the header and implementation files for your operations: + +#### 1. Header (`cpp/extensions//operations.h`) +Keep the header clean and specify exact JSI install functions: +```cpp +#pragma once +#include + +namespace rnexecutorch::extensions:: +{ + void install_customOp(facebook::jsi::Runtime &rt, facebook::jsi::Object &module); +} +``` + +#### 2. Source (`cpp/extensions//operations.cpp`) +* Extract input and output tensors as `TensorHostObject` pointers. +* Check bounds, shapes, types, and verify that the output tensor is **not the same instance** as the input (no unsafely managed in-place mutation). +* Lock tensors using `std::shared_lock` (for inputs) and `std::unique_lock` (for outputs). + +```cpp +#include "operations.h" +#include "core/tensor.h" +#include + +namespace rnexecutorch::extensions:: +{ + namespace jsi = facebook::jsi; + using TensorHostObject = rnexecutorch::core::tensor::TensorHostObject; + + void install_customOp(jsi::Runtime &rt, jsi::Object &module) + { + auto name = "customOp"; + auto fnBody = [](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value + { + // 1. Strict argument count validation (No default values here!) + if (count != 3) + { + throw jsi::JSError(rt, "Usage: customOp(src, dst, factor)"); + } + + // 2. Validate input and output types + auto srcObj = args[0].asObject(rt); + auto dstObj = args[1].asObject(rt); + if (!srcObj.isHostObject(rt) || !dstObj.isHostObject(rt)) + { + throw jsi::JSError(rt, "customOp: Arguments src and dst must be Tensors"); + } + + auto src = srcObj.getHostObject(rt); + auto dst = dstObj.getHostObject(rt); + double factor = args[2].asNumber(); + + // 3. Prevent in-place mutations + if (src.get() == dst.get()) + { + throw jsi::JSError(rt, "customOp: In-place operations (src == dst) are not supported."); + } + + // 4. Validate metadata compatibility + if (src->shape_ != dst->shape_ || src->dtype_ != dst->dtype_) + { + throw jsi::JSError(rt, "customOp: src and dst shape and dtype must match"); + } + + // 5. Lock underlying buffers + std::shared_lock src_lock(src->mutex_, std::try_to_lock); + std::unique_lock dst_lock(dst->mutex_, std::try_to_lock); + if (!src_lock.owns_lock() || !dst_lock.owns_lock()) + { + throw jsi::JSError(rt, "customOp: Tensors are currently in use"); + } + + if (!src->data_ || !dst->data_) + { + throw jsi::JSError(rt, "customOp: Tensor has been disposed"); + } + + // 6. Perform the computation + const float *srcData = reinterpret_cast(src->data_.get()); + float *dstData = reinterpret_cast(dst->data_.get()); + size_t size = src->size(); + + for (size_t i = 0; i < size; ++i) + { + dstData[i] = srcData[i] * static_cast(factor); + } + + // Always return the destination tensor (args[1]) as the JSI result + return jsi::Value(rt, args[1]); + }; + + module.setProperty(rt, name, jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, name), 3, fnBody)); + } +} +``` + +--- + +### Step 2: Register in Extension and Core JSI Installs + +1. **Extension Register** (`cpp/extensions//install.cpp`): + ```cpp + #include "install.h" + #include "operations.h" + + namespace rnexecutorch::extensions:: + { + void install(facebook::jsi::Runtime &rt, facebook::jsi::Object &module) + { + facebook::jsi::Object subModule(rt); + install_customOp(rt, subModule); + module.setProperty(rt, "", subModule); + } + } + ``` + +2. **Core Register** ([cpp/RnExecutorch.cpp](../cpp/RnExecutorch.cpp)): + ```cpp + #include "extensions//install.h" + // ... inside rnexecutorch::install ... + rnexecutorch::extensions::::install(jsiRuntime, myModule); + ``` + +--- + +### Step 3: TypeScript Bridge & Wrappers +Under `src/extensions/.ts` or `src/extensions//index.ts`: +* **Use the `rnexecutorchJsi` Symbol**: You must import and interact with native bindings using the `rnexecutorchJsi` symbol exported from [src/native/bridge.ts](../src/native/bridge.ts). **Do not** reference the global `__rnexecutorch_jsi__` directly throughout your wrapper files. +* Expose the TypeScript wrapper. +* Handle default values here instead of the C++ layer. +* Mark wrapper functions with the `"worklet";` directive. + +```typescript +import { rnexecutorchJsi } from '../native/bridge'; +import { type Tensor } from '../core/tensor'; + +/** + * Applies a custom operation scaling the src tensor by factor. + * @param src Input Tensor. + * @param dst Pre-allocated Destination Tensor. + * @param factor Scale factor. Defaults to 1.0. + */ +export function customOp(src: Tensor, dst: Tensor, factor: number = 1.0): Tensor { + 'worklet'; + return rnexecutorchJsi..customOp(src, dst, factor); +} +``` + +--- + +## 📋 Verification Checklist + +When adding a native extension, verify that: +- [ ] You only implemented in C++ if the operation takes `> 5%` of the total inference budget. +- [ ] No JSI Tensors are implicitly allocated and returned in the C++ code. +- [ ] Input and output tensors are locked using `std::shared_lock` and `std::unique_lock` respectively. +- [ ] In-place mutation is explicitly prevented by checking that `src != dst`. +- [ ] No default parameter values are defined in the C++ header/source files. +- [ ] The custom operation install function is registered in both the domain `install` function and core [cpp/RnExecutorch.cpp](../cpp/RnExecutorch.cpp). +- [ ] The TypeScript wrapper imports and uses `rnexecutorchJsi` instead of the global `__rnexecutorch_jsi__`. +- [ ] The TypeScript wrapper is marked with the `"worklet";` directive and defines all default parameter values. diff --git a/.agents/skills/add-task-pipeline/SKILL.md b/.agents/skills/add-task-pipeline/SKILL.md new file mode 100644 index 0000000000..ad1872ac06 --- /dev/null +++ b/.agents/skills/add-task-pipeline/SKILL.md @@ -0,0 +1,227 @@ +--- +name: add-task-pipeline +description: Use when creating a TypeScript task pipeline, implementing image preprocessing/postprocessing, loading models, or wrapping pipelines in React hooks. +metadata: + id: add_task_pipeline + scope: src/extensions/*/tasks/*, src/hooks/* +--- + +# Skill: Add a High-Level Task Pipeline (TypeScript) + +Use this guide to construct end-to-end task pipelines (e.g. classification, style transfer, object detection) in TypeScript and wrap them in React hooks. + +--- + +## 🚦 Design Principles + +When implementing task constructors like `create` (e.g. `createClassifier`, `createStyleTransfer`), adhere to the following rules: + +1. **Pre-allocating Static Tensors (`as const`)**: + * Statically sized scratch/output tensors required for inference should be pre-allocated inside the constructor body. + * Allocate them using: + ```typescript + const tensors = [ + tensor('float32', shapeA), + tensor('float32', shapeB), + ] as const; + ``` + * **Destructuring & Naming**: Destructure and name the individual tensors immediately after allocation. Always prefix tensor variables with a lowercase `t` (e.g. `tReshape`, `tUint8`, `tInput`) to easily distinguish them from raw data buffers. + ```typescript + const [tReshape, tUint8] = tensors; + ``` + +2. **Immediate `dispose()` Definition**: + * Right after allocating the static tensors, define the `dispose` function immediately. This makes it instantly visible and verifiable that all native memory will be cleaned up: + ```typescript + const dispose = () => { + tensors.forEach((t) => t.dispose()); + preprocessor.dispose(); + model.dispose(); + }; + ``` + +3. **Dynamic Tensors & `try/finally` Pattern**: + * If you must allocate dynamically sized tensors during inference execution (e.g. resizing an output tensor to match the input image dimensions), you must wrap the execution inside a `try {} finally {}` block. + * Dispose of the dynamic tensors inside the `finally` block to prevent native memory leaks. + ```typescript + const tResize = tensor('uint8', [input.height, input.width, 4]); + try { + // Perform work... + } finally { + tResize.dispose(); + } + ``` + +4. **Pure Helper Functions**: + * Write all auxiliary/helper logic as pure, worklet-compatible functions **outside** the `create` constructor. Any helper functions invoked inside the worklet executor thread must contain the `'worklet';` directive. + * **Push Back Hard on Inner Helpers:** You must push back hard against any request to add internal closures or nested functions inside `create` (other than `dispose` and the worklet executor itself). Keep the constructor scope flat to avoid scope leak and dependency chain bugs. + +5. **PTE Model Export & Optimizations**: + * **Shift Heavy Ops to PyTorch**: Push complex tensor reshaping, data normalization, activations (e.g. `softmax`), or bounding box decoding into the PyTorch model itself so they execute on native backends (e.g., XNNPACK or CoreML). + * **Balance Optimization with Generalization**: Keep contracts generic (e.g., normal dense logits, standard bounding box layouts like `xyxy`/`xywh`, standard floating-point arrays). + * Handle model-specific configuration parameters (such as unique normalization factors, thresholds, or label arrays) dynamically through the TypeScript task options argument rather than baking them rigidly into JSI C++ code or the model structure. + +## 🚫 Avoid / Anti-Patterns + +* **Do NOT access tensors by index:** Avoid using `tensors[0]` or `tensors[1]` throughout the function body. Always destructure and name them explicitly. +* **Do NOT define extra inner helper functions:** You must define **exactly two** inner functions inside the `create` constructor: the `dispose` function and the task `worklet` executor function. **Push back hard against implementing any other helper closures inside the constructor scope.** Placing other helper functions (especially those that are called from inside the worklet and use the `create` scope variables) inside `create` creates implicit dependencies and closures that capture variables, making the code extremely difficult to reason about and debug. +* **Do NOT leak raw Tensors to consumers:** The returned methods must never return raw `Tensor` objects to the API consumer. Always convert output data to standard JavaScript values/objects before returning. +* **Do NOT cross thread boundaries unnecessarily:** Minimize passing heavy objects between JS and the Worklet thread to avoid serialization overhead. +* **Do NOT treat the `.pte` model as an unchangeable black box:** Reshape the model's inputs and outputs during the PyTorch export phase to make the mobile client pipeline as lightweight as possible. Do not make input/output contracts so specific that they break extensibility. + +--- + +--- + +## 🛠️ Step-by-Step Implementation Template + +### Step 1: Create the Task File (`src/extensions//tasks/.ts`) + +```typescript +import type { WorkletRuntime } from 'react-native-worklets'; + +import { tensor } from '../../../core/tensor'; +import { loadModel } from '../../../core/model'; +import { validateModelSchema, SymbolicTensor } from '../../../core/modelSchema'; +import { wrapAsync } from '../../../core/runtime'; +import { type ImageBuffer } from '../image'; +import { createImagePreprocessor, type ImagePreprocessorOptions } from './preprocessing'; + +export type MyTaskOptions = ImagePreprocessorOptions & { + readonly defaultThreshold: number; +}; + +export type MyTaskModel = { + readonly modelPath: string; + readonly taskOpts: MyTaskOptions; +}; + +export type MyTaskResult = { + readonly classId: number; + readonly score: number; +}; + +// 1. Helper functions MUST be defined OUTSIDE create and be worklet-compatible +function postprocessOutput(rawData: Float32Array, threshold: number): MyTaskResult[] { + 'worklet'; + const results: MyTaskResult[] = []; + for (let i = 0; i < rawData.length; i++) { + if (rawData[i]! > threshold) { + results.push({ classId: i, score: rawData[i]! }); + } + } + return results.sort((a, b) => b.score - a.score); +} + +export async function createMyTask( + config: MyTaskModel, + runtime?: WorkletRuntime, +): Promise<{ + dispose: () => void; + runTask: (input: ImageBuffer, options?: { threshold?: number }) => Promise; + runTaskWorklet: (input: ImageBuffer, options?: { threshold?: number }) => MyTaskResult[]; +}> { + const { modelPath, taskOpts } = config; + const model = await wrapAsync(loadModel, runtime)(modelPath); + + // Validate model schema + const meta = validateModelSchema( + model, + 'forward', + [SymbolicTensor('float32', [1, 3, 'H', 'W'], [3, 'H', 'W'])], + [SymbolicTensor('float32', [1, 10], [10])], + ); + const inpShape = meta.inputTensorMeta[0]!.shape; + const outShape = meta.outputTensorMeta[0]!.shape; + + // 2. Pre-allocate static tensors + const tensors = [ + tensor('float32', outShape), + ] as const; + + // Idiomatic destructuring and naming with "t" prefix + const [tOutput] = tensors; + const preprocessor = createImagePreprocessor(taskOpts, inpShape); + + // 3. Define dispose() immediately after allocation + const dispose = () => { + preprocessor.dispose(); + tensors.forEach((t) => t.dispose()); + model.dispose(); + }; + + // 4. Define exactly two inner functions (dispose & runTaskWorklet) + const runTaskWorklet = ( + input: ImageBuffer, + options?: { threshold?: number } + ): MyTaskResult[] => { + 'worklet'; + + // Process input buffer to input tensor + const tInput = preprocessor.process(input); + model.execute('forward', [tInput], [tOutput]); + + const data = tOutput.getData(new Float32Array(tOutput.numel)); + const threshold = options?.threshold ?? taskOpts.defaultThreshold; + + // 5. Return standard JS object, never raw Tensor + return postprocessOutput(data, threshold); + }; + + const runTask = wrapAsync(runTaskWorklet, runtime); + + return { runTask, runTaskWorklet, dispose }; +} +``` + +### Step 2: Create the React Hook Wrapper (`src/hooks/use.ts`) + +Wrap the task pipeline in a custom React Hook using the core hooks `useModelDownload` and `useModel`. This manages downloading, compilation, error tracking, and automatic cleanup of the native memory upon unmounting or config changes. + +```typescript +import { useModel } from './useModel'; +import { useModelDownload } from './useModelDownload'; +import { createMyTask, type MyTaskModel } from '../extensions//tasks/'; + +export function useMyTask( + config: MyTaskModel, + options?: { preventLoad?: boolean } +) { + // 1. Resolve remote or local asset model path and download progress + const { localPath, downloadProgress, downloadError } = useModelDownload( + config.modelPath, + options?.preventLoad, + ); + + // 2. Instantiate and compile the task pipeline (with automatic lifecycle cleanup) + const { model, error } = useModel( + createMyTask, + localPath ? { ...config, modelPath: localPath } : null, + [localPath], + ); + + return { + isReady: !!model, + error: downloadError || error, + downloadProgress, + localPath, + runTask: model?.runTask, + runTaskWorklet: model?.runTaskWorklet, + }; +} +``` + +--- + +## 📋 Verification Checklist + +When adding a task pipeline or React hook, verify that: +- [ ] Scratch/output tensors are pre-allocated using `tensor() as const` and prefixed with lowercase `t` (e.g. `tInput`). +- [ ] Static tensors are destructured and named (no index-based access in the body). +- [ ] The `dispose` function is defined immediately after static allocations. +- [ ] Any dynamically allocated tensors are wrapped in `try/finally` and disposed of inside `finally`. +- [ ] The constructor contains exactly two inner functions (the `dispose` function and the worklet executor). +- [ ] Auxiliary helpers are defined outside the constructor and marked with the `'worklet';` directive if run on the worklet runtime. +- [ ] Raw `Tensor` objects are never returned to the consumer. +- [ ] Data configurations (e.g. thresholds, labels) are configurable dynamically via the TypeScript task options. +- [ ] The React Hook utilizes `useModel` and properly returns progress, ready state, errors, and task execution bindings. diff --git a/.agents/skills/core-guidelines/SKILL.md b/.agents/skills/core-guidelines/SKILL.md new file mode 100644 index 0000000000..06745e617b --- /dev/null +++ b/.agents/skills/core-guidelines/SKILL.md @@ -0,0 +1,117 @@ +--- +name: core-guidelines +description: Use when learning the codebase architecture, design patterns, core/extension symmetry, file structure, or general coding standards. +metadata: + id: core_guidelines + scope: general +--- + +# RNET-POC Agent Skills & Architecture Guide + +Welcome, Agent! This directory contains specialized "skills" (recipes) designed to help you extend this codebase in an idiomatic, clean, and consistent manner. + +Before implementing any feature or modifying code, please find the corresponding skill folder in `.agents/skills/` and read its `SKILL.md` using the `IsSkillFile: true` option to ensure you follow the correct patterns. + +--- + +## 🎯 Design Philosophy + +The library is designed around the separation of responsibilities into two distinct layers: +1. **Lower-Level API (Flexible & Domain-Agnostic)**: Exposes raw bindings to native ExecuTorch capabilities (tensors, models, runtime execution, and basic math/CV operations) in C++ without task-specific code. This layer is designed to be highly flexible, allowing developer customization directly in TypeScript. +2. **Higher-Level API (Transparent & Orchestrated)**: Built as orchestration layers **entirely in TypeScript** on top of the lower-level native bindings. Preprocessing, running model inference, and output postprocessing are composed dynamically in TS. + +* **Why this matters**: In contrast to opaque native pipelines, implementing pipelines in TypeScript makes them transparent, easy to debug, highly customizable, and easy to extend without writing complex native C++ code. + +--- + +## 🏛️ Symmetrical Architecture (Core vs. Extensions) + +This library is strictly structured using a symmetrical layout between the native C++ layer (`cpp/`) and the TypeScript layer (`src/`). When adding a new capability, you will typically need to modify or add files in both layers under their respective subfolders: + +```text +cpp/ │ src/ +├── core/ │ ├── core/ +│ ├── install.h │ │ ├── model.ts +│ ├── tensor.h │ │ ├── tensor.ts +│ └── ... │ │ └── ... +└── extensions/ │ ├── extensions/ + ├── math/ │ │ ├── math.ts + │ ├── install.h │ │ └── cv/ + │ └── operations.h │ │ ├── image.ts + └── cv/ │ │ └── tasks/ + ├── install.h │ │ └── classification.ts + └── image_ops.h │ └── hooks/ + │ └── useClassifier.ts +``` + +### 1. Core (`cpp/core/` and `src/core/`) +* **Purpose**: Implements the absolute bare minimum required for the lower-level API. +* **Responsibilities**: Primitives for `Tensor` and `Model` management, JSI bindings, and the ExecuTorch runtime execution logic. +* **Rule**: Core is domain-agnostic. **Do not** add task-specific or domain-specific code (like computer vision ops, tokenizers, or speech preprocessing) here. + +### 2. Extensions (`cpp/extensions/` and `src/extensions/`) +* **Purpose**: Contains all domain-specific logic, organized by domain (e.g., `cv`, `math`, `llm`, `speech`). +* **Symmetry Rules**: + * **Native operations** (e.g. image transformations, box math) go under `cpp/extensions//`. + * **JSI Installation** is registered via `cpp/extensions//install.cpp` and exposed on the global JSI object under `__rnexecutorch_jsi__.`. + * **TypeScript wrappers** for these native operations go in `src/extensions/.ts` or `src/extensions//`. + * **Higher-level task pipelines** (orchestration of model loading, input pre-processing, running inference, and output post-processing) are written entirely in TypeScript under `src/extensions//tasks/`. + * **React Hooks** (lightweight state/lifecycle wrappers around task pipelines) go in `src/hooks/`. + +--- + +## 📂 Skills Index + +Use the following index to locate the specific procedural guides for your task: + +| I want to... | Use this Skill File | Description | +| :--- | :--- | :--- | +| **Add a new native operator or C++ binding** | [SKILL.md](../add-native-extension/SKILL.md) | Procedural guide to implementing C++ functions, exposing them via JSI, and writing TypeScript bridge wrappers. | +| **Create a task pipeline or hook** | [SKILL.md](../add-task-pipeline/SKILL.md) | Guide to building end-to-end TS pipelines (e.g. object detection) and exposing them via React hooks. | +| **Verify, rebuild, or troubleshoot changes** | [SKILL.md](../verify-and-build/SKILL.md) | Workflows for rebuilding TS/C++ and resolving common JSI runtime errors. | +| **Validate model constraints & schemas** | [SKILL.md](../model-schema-validation/SKILL.md) | Guide on specifying SymbolicTensor constraints and shapes for model signature validation. | + +--- + +## 💡 Key Coding Conventions for Agents + +* **Worklets**: Ensure all TypeScript functions directly wrapping native JSI calls start with the `"worklet";` directive so they are compatible with worklet-based libraries (e.g., React Native Reanimated). +* **Memory Management**: When writing native C++ code with JSI, pay close attention to JSI reference management and handle ExecuTorch lifecycle states safely. +* **Keep Core Clean**: Always build on top of core primitives. Do not modify files in `cpp/core/` or `src/core/` unless you are fixing a bug in the foundational runtime. + +--- + +## 📢 Package Registration & Exports + +Whenever you add a new extension, task pipeline, or hook, you **must** register and export them at the package level to make them accessible to users: +1. **TypeScript Exports** ([src/index.ts](../src/index.ts)): Export the new task pipelines (e.g. `export * from './extensions/cv/tasks/myTask'`) and hooks (e.g. `export * from './hooks/useMyTask'`). +2. **Model Configurations** ([src/models.ts](../src/models.ts)) & **Constants** ([src/constants.ts](../src/constants.ts)): + * These **two files are the sole source of truth** for defining pre-exported models, their options, and their label structures. Do not define models or options ad-hoc anywhere else. + * **Model Registry Rule**: You must **only export the single `models` object registry** from `models.ts`. Do not export individual model configuration constants. Define them as internal (private) `const` variables inside `models.ts` and register them under the appropriate category nested inside the exported `models` registry object. + * If you are introducing a standard pre-configured model, add its metadata config and HuggingFace/local URI endpoint mapping here. + +--- + +## 🔍 Model Inspection & Testing via the Example App + +The example app under `example/` is the primary playground for inspecting models and testing your pipelines on-device: +1. **Inspect Screen** ([InspectScreen.tsx](../example/src/InspectScreen.tsx)): Paste any arbitrary `.pte` model URL to load and print its metadata, inputs, and outputs dynamically. +2. **Gallery Screen** ([GalleryScreen.tsx](../example/src/GalleryScreen.tsx)): Test task pipelines (classification, detection, style transfer, segmentation) on static assets/images. When adding a new task, you should add a corresponding interactive testing card/flow here. +3. **Camera Screen** ([CameraScreen.tsx](../example/src/CameraScreen.tsx)): Run real-time camera-based inferences using the task pipelines. + +--- + +## 🛠️ Verification & Rebuilding Checklist + +To test and compile your changes: +1. **TS Build & Typecheck**: + * **Verify Types (Fast)**: `yarn typecheck` + * **Build Bundles**: `yarn prepare` *(runs `react-native-builder-bob` to bundle TS source into the `lib/` directory)* +2. **Native Rebuilding (iOS)**: + Whenever native C++ files are added or modified: + ```bash + cd example/ios && pod install && cd ../.. + ``` + *Then run the example app using `yarn example ios` (or `yarn ios` from `example/`).* +3. **Native Rebuilding (Android)**: + *Android builds handle C++ file changes automatically upon triggering `yarn example android` (or `yarn android` from `example/`).* diff --git a/.agents/skills/model-schema-validation/SKILL.md b/.agents/skills/model-schema-validation/SKILL.md new file mode 100644 index 0000000000..21a2f70bc3 --- /dev/null +++ b/.agents/skills/model-schema-validation/SKILL.md @@ -0,0 +1,123 @@ +--- +name: model-schema-validation +description: Use when defining SymbolicTensor constraints, validating ExecuTorch model shapes, checking method signatures, or verifying dynamic tensor dimensions. +metadata: + id: model_schema_validation + scope: src/core/modelSchema.ts, src/extensions/*/tasks/* +--- + +# Skill: Model Schema Validation Guide + +Use this guide to define and enforce structural constraints (shapes and data types) on loaded ExecuTorch `.pte` models using `validateModelSchema`. + +--- + +## 🔍 Why Validate Model Schemas? + +Every ExecuTorch model exposes metadata about its execution methods (typically `'forward'`), including: +* Input and output argument counts. +* The expected types (primitives like `number`/`boolean` or `Tensor`). +* The data type (`float32`, `int32`, etc.) and the shape arrays of tensors. + +To prevent runtime crashes and memory allocation mismatches in C++, the TypeScript task pipeline must validate that the provided model matches its expected execution signature *before* allocating static tensors or executing inference. + +--- + +## 🛠️ Validation API Reference + +```typescript +import { validateModelSchema, SymbolicTensor } from '../../../core/modelSchema'; + +const meta = validateModelSchema( + model, + methodName, + expectedInputs, + expectedOutputs +); +``` + +### Constraints Types: +* **Primitives**: `'number' | 'boolean' | 'null'` +* **Tensors**: Defined via the `SymbolicTensor(dtype?, ...shapes)` helper. + +--- + +## 📏 Symbolic Dimensions & Dynamic Shapes + +Tensors often support dynamic dimensions (such as varying image sizes `'H'`, `'W'` or dynamic batch/prediction counts `'N'`). +The `SymbolicTensor` helper supports specifying **Symbolic Shapes** where integers are static matching constraints, and string names act as runtime variables. + +### How Symbolic Matching Works: +1. **Numbers (Static Match)**: + If a dimension is defined as a number, the loaded model's tensor dimension must match that exact integer. + * *Example*: `[1, 3, 'H', 'W']` requires the batch dimension to be exactly `1`, and channels to be exactly `3`. + +2. **Strings (Symbolic Match)**: + If a dimension is defined as a string (e.g., `'H'`), the validator binds the actual dimension size to that symbol name. + * *Symbolic Constraint Rules*: Within a single tensor, if a symbol (e.g., `'H'`) appears multiple times, the corresponding dimensions must be equal. + +3. **Multiple Alternative Shapes**: + You can provide multiple shape variations to `SymbolicTensor` to support models compiled in different layouts (e.g., channels-first vs. channels-last). + * *Example*: `SymbolicTensor('float32', [1, 3, 'H', 'W'], [3, 'H', 'W'])` allows either 4D or 3D float32 layouts. + +--- + +## 📋 Common Validation Recipes + +### 1. Classification +Accepts an image tensor and outputs a 2D class probabilities array: +```typescript +const meta = validateModelSchema( + model, + 'forward', + [SymbolicTensor('float32', [1, 3, 'H', 'W'], [3, 'H', 'W'])], // Input + [SymbolicTensor('float32', [1, 'N'], ['N'])] // Output: logits / probs +); + +const inpShape = meta.inputTensorMeta[0]!.shape; +const outShape = meta.outputTensorMeta[0]!.shape; +``` + +### 2. Image-to-Image / Style Transfer +Accepts an image tensor and returns a modified image tensor with identical dimensions: +```typescript +const meta = validateModelSchema( + model, + 'forward', + [SymbolicTensor('float32', [1, 3, 'H', 'W'], [3, 'H', 'W'])], // Input + [SymbolicTensor('float32', [1, 3, 'H', 'W'], [3, 'H', 'W'])] // Output +); +``` + +### 3. Object Detection (Dynamic Boxes Count) +Accepts an image tensor, and outputs boxes, scores, and class labels for `N` dynamic detections: +```typescript +const meta = validateModelSchema( + model, + 'forward', + [SymbolicTensor('float32', [1, 3, 'H', 'W'], [3, 'H', 'W'])], + [ + SymbolicTensor('float32', ['N', 4]), // Bounding boxes (xyxy / xywh) + SymbolicTensor('float32', ['N']), // Prediction confidence scores + SymbolicTensor('float32', ['N']), // Predicted class labels + ] +); +``` + +--- + +## 🚫 Avoid / Anti-Patterns + +* **Do NOT write imperative size/type checks manually:** Avoid writing custom shape/type assertion blocks (e.g., `if (tensor.shape[0] !== 1)`). Always use the declarative `validateModelSchema` utility, which reports unified, readable mismatch errors. +* **Do NOT use hardcoded integers for dynamic dimensions:** If a shape can vary (e.g., dynamic height, width, or batch sizes), use a string symbol (like `'H'`, `'W'`, `'N'`) to allow dynamic matching. +* **Do NOT skip validation at startup:** Always validate the model schema *before* creating pre-allocated static tensors, preventing native memory crashes from mismatched layouts. + +--- + +## 📋 Verification Checklist + +When specifying model schema validations, verify that: +- [ ] Schema validation is performed immediately after model loading and before tensor initialization. +- [ ] All dynamic dimensions (e.g., dynamic box counts, channels-last heights/widths) are defined as string symbols. +- [ ] Multiple shape variations are provided to `SymbolicTensor` if channels-first and channels-last layouts are both supported. +- [ ] Input and output constraints map accurately to standard model specifications (e.g. dense logits, standard bounding boxes layouts). diff --git a/.agents/skills/verify-and-build/SKILL.md b/.agents/skills/verify-and-build/SKILL.md new file mode 100644 index 0000000000..0fc3cf3ae6 --- /dev/null +++ b/.agents/skills/verify-and-build/SKILL.md @@ -0,0 +1,148 @@ +--- +name: verify-and-build +description: Use when compiling TS/C++, rebuilding iOS pods, troubleshooting JSI runtime errors, debugging native crashes, or sync'ing Android Gradle. +metadata: + id: verify_and_build + scope: general development, example/ +--- + +# Skill: Verification, Compilation & Troubleshooting + +Use this guide to compile, test, verify, and troubleshoot your modifications to the library (both JS/TS and native C++). + +--- + +## 🛠️ Verification & Compilation Workflows + +### 1. Build and Typecheck TypeScript +To check types and compile the TypeScript source code: +* **Verify Types (Fast)**: + ```bash + yarn typecheck + ``` +* **Build Bundles**: + ```bash + yarn prepare + ``` + *This cleans target directories and builds modules into the `lib/` directory using `react-native-builder-bob`.* +* **When**: After adding/updating TypeScript files under `src/`. +* **Verification**: Ensure no compiler or type-checking errors are raised. + +### 2. Native Rebuilding + +#### iOS Development +Whenever you modify native files under `cpp/` or update `.podspec`: +```bash +cd example/ios && pod install && cd ../.. +``` +* **Rebuilding**: Re-run the app from the root workspace or from the `example/` folder: + ```bash + # From root: + yarn example ios + # OR from example/ directory: + yarn ios + ``` +* **Clean Build**: If files aren't being picked up: + * In Xcode: `Product` > `Clean Build Folder` (Cmd + Shift + K). + +#### Android Development +Android compiles C++ source files on-demand during application builds: +```bash +# From root: +yarn example android +# OR from example/ directory: +yarn android +``` +* **Clean Build**: If caching issues occur: + ```bash + cd android && ./gradlew clean && cd .. + ``` + +--- + +## 🔍 Debugging & Log Access + +### 1. TypeScript & Worklet Logging +Logging inside worklets (functions annotated with `'worklet';`) behaves slightly differently as they run on a separate runtime thread. +* Use standard `console.log(...)` inside worklets. +* **Caution**: Worklet logs are piped back to the JS console, but passing complex circular objects to `console.log` from a worklet can fail or freeze. Stringify or log specific primitive properties instead. + +### 2. Native C++ Debugging +Native JSI crashes, standard outputs, or ExecuTorch errors can be caught in the native log streams: +* **iOS**: Watch outputs directly inside the Xcode Console while running the example target. +* **Android**: Use Android Studio's logcat or run: + ```bash + adb logcat *:S ReactNative:V ReactNativeJS:V rnexecutorch:V + ``` + +--- + +## 🚦 Common JSI Troubleshooting + +### Symptom: `Error: JSI global object '__rnexecutorch_jsi__' is not registered.` +* **Cause 1**: The native code wasn't linked or JSI installation failed during loading. + * *Fix*: Verify that you have registered your new extension/module in [cpp/RnExecutorch.cpp](../cpp/RnExecutorch.cpp). + * *Fix*: Re-run `pod install` (for iOS) or perform a Gradle sync/clean (for Android) to ensure your C++ changes compiled. +* **Cause 2**: The bridge did not invoke the native installation. + * *Fix*: Ensure the JS entrypoint triggers the load (see [src/native/bridge.ts](../src/native/bridge.ts)). + +### Symptom: `TypeError: Cannot read property '' of undefined` +* **Cause**: You added an extension in C++ but forgot to register it in your TypeScript bridge bindings. + * *Fix*: Check that `module.setProperty(rt, "", subModule)` is called in your native `install.cpp` and that your TS wrapper exports and references it under `rnexecutorchJsi.`. + +--- + +## 📂 Model Hosting & Download Caching + +This project does **not** bundle local `.pte` model files inside the React Native application package. Instead, the standard workflow is: + +1. **Upload to Hugging Face**: + * Upload compiled/exported `.pte` models directly to our Hugging Face repository bucket. + * **Naming Convention**: All `.pte` files must follow the strict name contract: + `modelname_optionalsize_backend_precision.pte` + * *Example*: `efficientnet_v2_s_xnnpack_int8.pte` + * *Example*: `style_transfer_candy_xnnpack_fp32.pte` + +2. **Define in Models Manifest** ([src/models.ts](../src/models.ts)): + * **Single Export Registry Rule**: Define the new model configuration as an internal (private) `const` inside `models.ts`. **Do not** export individual model configuration constants. Instead, expose them solely by registering them under the appropriate category nested inside the main `models` registry object, which is the only exported symbol from `models.ts`. + * *Example*: + ```typescript + // Defined internally (private) + const MY_MODEL_XNNPACK_FP32: MyTaskModel = { ... }; + + // Registered in the exported models object + export const models = { + ... + myTask: { + MY_MODEL: { + XNNPACK_FP32: MY_MODEL_XNNPACK_FP32, + } + } + }; + ``` + +3. **Runtime Downloading & Caching**: + * The custom hook `useModelDownload(config.modelPath)` handles checks automatically: + * If the model file is not already in the device cache directory, it downloads it from the Hugging Face URL to `RNFS.CachesDirectoryPath`. + * If the model file exists in the cache, it bypasses the download and returns the cached local path immediately to load the model. + +--- + +## 🚫 Avoid / Anti-Patterns + +* **Do NOT run code without verification:** Do not test TypeScript changes in the example app without first running `yarn typecheck` (verify types) and `yarn prepare` (build target bundles). +* **Do NOT skip native rebuilds after C++ edits:** If any C++ files or config bindings are added/modified, do not attempt to run the app without executing `pod install` (for iOS) or letting Gradle sync (for Android). +* **Do NOT log complex objects inside worklets:** Avoid passing complex circular objects directly to `console.log()` inside functions annotated with `'worklet';` as it can hang or crash the worklet runtime thread. +* **Do NOT bundle local `.pte` files in the repository:** Do not commit heavy model binaries to the git repository. Always host them on Hugging Face and register their metadata in `src/models.ts`. + +--- + +## 📋 Verification Checklist + +When verifying or compiling your modifications, check that: +- [ ] TypeScript typechecking passes without errors (`yarn typecheck`). +- [ ] Bundles compile successfully (`yarn prepare`). +- [ ] `pod install` has been run inside `example/ios/` after any native C++ edits. +- [ ] No `.pte` files are staged or added to git history. +- [ ] Model configurations are registered inside the single `models` object registry in `src/models.ts`. +- [ ] Native runtime issues have been investigated using Xcode Console or `adb logcat`. diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..1598380163 --- /dev/null +++ b/.clang-format @@ -0,0 +1,16 @@ +# clang-format configuration for the react-native-executorch C/C++/Objective-C +# sources. LLVM-based with attached braces, 4-space indentation, and no fixed +# column limit: clang-format's aggressive wrapping to satisfy a column limit +# tends to produce awkward, less-readable breaks, so developers handle line +# breaks for long lines manually. See issue #1217 and the PR #1256 discussion. +BasedOnStyle: LLVM +Standard: c++20 +UseTab: Never +IndentWidth: 4 +TabWidth: 4 +ColumnLimit: 0 +AccessModifierOffset: -4 +AllowShortIfStatementsOnASingleLine: false +IndentCaseLabels: false +FixNamespaceComments: false +InsertNewlineAtEOF: true diff --git a/.cspell-wordlist.txt b/.cspell-wordlist.txt index 3837e96955..bba6f6b6dd 100644 --- a/.cspell-wordlist.txt +++ b/.cspell-wordlist.txt @@ -132,6 +132,12 @@ detr metaprogramming ktlint lefthook +dtype +numel +metas +Metas +ndim +nbytes espeak KOMODO DUNGENESS diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a06b1c5f58..880570040f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,6 @@ updates: directories: - "/" - "/packages/*" - - "/apps/*" - "/docs" schedule: interval: "monthly" diff --git a/.github/workflows/build-android-llm-example.yml b/.github/workflows/build-android-llm-example.yml deleted file mode 100644 index 3b4c95bf58..0000000000 --- a/.github/workflows/build-android-llm-example.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: LLM Example app Android build check -on: - pull_request: - paths: - - .github/workflows/build-android-llm-example.yml - - apps/llm/** - - packages/react-native-executorch/** - push: - branches: - - main - paths: - - .github/workflows/build-android-llm-example.yml - - apps/llm/** - - packages/react-native-executorch/** - workflow_dispatch: -jobs: - build: - if: github.repository == 'software-mansion/react-native-executorch' - runs-on: ubuntu-latest - env: - WORKING_DIRECTORY: apps/llm - concurrency: - group: android-${{ github.ref }} - cancel-in-progress: true - steps: - - name: Free Disk Space (Manual) - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf /opt/hostedtoolcache/CodeQL - sudo docker system prune -af - - - name: Check out Git repository - uses: actions/checkout@v6 - with: - submodules: recursive - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: "24" - cache: "yarn" - - name: Setup Java 17 - uses: actions/setup-java@v5 - with: - distribution: "zulu" - java-version: 17 - cache: "gradle" - - name: Install root dependencies - run: yarn install --immutable - - name: Install Expo CLI - run: | - npm install -g @expo/cli - echo "$(npm prefix -g)/bin" >> $GITHUB_PATH - - name: Generate native Android project - working-directory: ${{ env.WORKING_DIRECTORY }} - run: | - rm -rf android - npx expo prebuild --platform android --no-install - - name: Cache Gradle - uses: actions/cache@v5 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - ${{ env.WORKING_DIRECTORY }}/android/.gradle - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Build app - working-directory: ${{ env.WORKING_DIRECTORY }}/android - run: | - ./gradlew assembleDebug \ - --build-cache \ - --parallel \ - --daemon \ - --configure-on-demand \ - -PreactNativeArchitectures=arm64-v8a \ - -Dorg.gradle.jvmargs="-Xmx4g -XX:+HeapDumpOnOutOfMemoryError" \ - -Dorg.gradle.workers.max=4 diff --git a/.github/workflows/build-ios-llm-example.yml b/.github/workflows/build-ios-llm-example.yml deleted file mode 100644 index 4a2b2b9b99..0000000000 --- a/.github/workflows/build-ios-llm-example.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: LLM Example app iOS build check -on: - push: - branches: - - main - paths: - - ".github/workflows/build-ios-llm-example.yml" - - "*.podspec" - - "apps/llm/**" - - "packages/react-native-executorch/**" - pull_request: - paths: - - ".github/workflows/build-ios-llm-example.yml" - - "*.podspec" - - "apps/llm/**" - - "packages/react-native-executorch/**" - workflow_dispatch: -jobs: - build: - if: github.repository == 'software-mansion/react-native-executorch' - runs-on: macos-latest - concurrency: - group: ios-${{ github.ref }} - cancel-in-progress: true - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: latest-stable - - name: Check out Git repository - uses: actions/checkout@v6 - with: - submodules: recursive - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: "24" - cache: "yarn" - - name: Install root dependencies - run: yarn install --immutable - - name: Install Expo CLI - run: | - npm install -g @expo/cli - echo "$(npm prefix -g)/bin" >> $GITHUB_PATH - - name: Generate native iOS project - working-directory: apps/llm - run: | - rm -rf ios - npx expo prebuild --platform ios --no-install - - name: Install CocoaPods dependencies - working-directory: apps/llm/ios - run: | - pod install - - name: Build app - working-directory: apps/llm/ios - run: | - set -o pipefail && xcodebuild \ - -workspace llm.xcworkspace \ - -scheme llm \ - -sdk iphonesimulator \ - -configuration Debug \ - -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \ - build \ - CODE_SIGNING_ALLOWED=NO \ - -jobs $(sysctl -n hw.ncpu) \ - COMPILER_INDEX_STORE_ENABLE=NO \ - ONLY_ACTIVE_ARCH=YES | xcbeautify diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a02381a3b8..bc595841d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,9 +3,11 @@ on: push: branches: - main + - rne-rewrite pull_request: branches: - main + - rne-rewrite merge_group: types: - checks_requested @@ -24,15 +26,7 @@ jobs: run: yarn lint - name: Typecheck files - run: yarn workspaces foreach --all --topological-dev run prepare && yarn typecheck - - - name: Verify error codes are up to date - run: | - yarn codegen:errors - git diff --exit-code -- \ - packages/react-native-executorch/src/errors/ErrorCodes.ts \ - packages/react-native-executorch/common/rnexecutorch/ErrorCodes.h \ - || { echo "::error::Error codes are stale. Run 'yarn codegen:errors' and commit the result."; exit 1; } + run: yarn workspaces foreach --all --exclude react-native-executorch-bare-resource-fetcher --exclude react-native-executorch-expo-resource-fetcher --exclude react-native-executorch-webrtc --topological-dev run prepare && yarn typecheck build-library: runs-on: ubuntu-latest @@ -44,4 +38,4 @@ jobs: uses: ./.github/actions/setup - name: Build all packages - run: yarn workspaces foreach --all --topological-dev run prepare + run: yarn workspaces foreach --all --exclude react-native-executorch-bare-resource-fetcher --exclude react-native-executorch-expo-resource-fetcher --exclude react-native-executorch-webrtc --topological-dev run prepare diff --git a/.gitignore b/.gitignore index 86ce3a9042..9595a23273 100644 --- a/.gitignore +++ b/.gitignore @@ -88,22 +88,18 @@ lib/ ios/generated android/generated apps/*/ios/ -!apps/bare_rn/ios/ apps/*/android/ -!apps/bare_rn/android/ # generated TypeDoc API reference (regenerated at build time) docs/docs/06-api-reference/ -# integration test model assets -packages/react-native-executorch/common/rnexecutorch/tests/integration/assets/models/ - # custom *.tgz Makefile *.pte -.agents +.agents/* +!.agents/skills/ .claude skills-lock.json diff --git a/apps/bare-rn/.bundle/config b/apps/bare-rn/.bundle/config deleted file mode 100644 index 848943bb52..0000000000 --- a/apps/bare-rn/.bundle/config +++ /dev/null @@ -1,2 +0,0 @@ -BUNDLE_PATH: "vendor/bundle" -BUNDLE_FORCE_RUBY_PLATFORM: 1 diff --git a/apps/bare-rn/.eslintrc.js b/apps/bare-rn/.eslintrc.js deleted file mode 100644 index 1d1d89f4b4..0000000000 --- a/apps/bare-rn/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - root: true, - extends: '@react-native', - parserOptions: { - requireConfigFile: false, - }, -}; diff --git a/apps/bare-rn/.gitignore b/apps/bare-rn/.gitignore deleted file mode 100644 index 874daf3051..0000000000 --- a/apps/bare-rn/.gitignore +++ /dev/null @@ -1,80 +0,0 @@ -# OSX -# -.DS_Store - -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate -**/.xcode.env.local - -# Android/IntelliJ -# -build/ -.idea -.gradle -local.properties -*.iml -*.hprof -.cxx/ -*.keystore -!debug.keystore -.kotlin/ - -# node.js -# -node_modules/ -npm-debug.log -yarn-error.log - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the -# screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/ - -**/fastlane/report.xml -**/fastlane/Preview.html -**/fastlane/screenshots -**/fastlane/test_output - -# Bundle artifact -*.jsbundle - -# Ruby / CocoaPods -**/Pods/ -/vendor/bundle/ - -# Temporary files created by Metro to check the health of the file watcher -.metro-health-check* - -# testing -/coverage - -# Yarn -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions - -# Custom -!/ios/ -!/android/ -/assets/ai-models/ diff --git a/apps/bare-rn/.prettierrc.js b/apps/bare-rn/.prettierrc.js deleted file mode 100644 index 06860c8d1b..0000000000 --- a/apps/bare-rn/.prettierrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - arrowParens: 'avoid', - singleQuote: true, - trailingComma: 'all', -}; diff --git a/apps/bare-rn/.watchmanconfig b/apps/bare-rn/.watchmanconfig deleted file mode 100644 index 0967ef424b..0000000000 --- a/apps/bare-rn/.watchmanconfig +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/apps/bare-rn/App.tsx b/apps/bare-rn/App.tsx deleted file mode 100644 index cb6af0aa33..0000000000 --- a/apps/bare-rn/App.tsx +++ /dev/null @@ -1,405 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { - ActivityIndicator, - Keyboard, - KeyboardAvoidingView, - Modal, - Platform, - ScrollView, - StyleSheet, - Text, - TextInput, - TouchableOpacity, - TouchableWithoutFeedback, - View, -} from 'react-native'; -import { models, initExecutorch, useLLM } from 'react-native-executorch'; -import { BareResourceFetcher } from 'react-native-executorch-bare-resource-fetcher'; -import { setConfig } from '@kesha-antonov/react-native-background-downloader'; -import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; - -// Configure Background Downloader logging -setConfig({ - isLogsEnabled: true, - logCallback: log => { - console.log('[BackgroundDownloader]', log); - }, -}); - -// Initialize Executorch with bare adapter -initExecutorch({ - resourceFetcher: BareResourceFetcher, -}); - -const ColorPalette = { - primary: '#001A72', - blueLight: '#C1C6E5', - blueDark: '#6676AA', - white: '#FFFFFF', - gray100: '#F5F5F5', - gray200: '#E0E0E0', -}; - -function Spinner({ - visible, - textContent, -}: { - visible: boolean; - textContent: string; -}) { - return ( - - - - - {textContent} - - - - ); -} - -const spinnerStyles = StyleSheet.create({ - overlay: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: 'rgba(0, 0, 0, 0.5)', - }, - container: { - padding: 25, - alignItems: 'center', - justifyContent: 'center', - }, - text: { - marginTop: 15, - color: ColorPalette.white, - fontSize: 18, - fontWeight: 'bold', - }, -}); - -function ErrorBanner({ - message, - onDismiss, -}: { - message: string | null; - onDismiss: () => void; -}) { - if (!message) return null; - return ( - - - {message} - - - - - - ); -} - -const errorBannerStyles = StyleSheet.create({ - container: { - backgroundColor: '#FEE2E2', - borderLeftWidth: 4, - borderLeftColor: '#EF4444', - borderRadius: 8, - marginHorizontal: 16, - marginVertical: 8, - paddingVertical: 10, - paddingLeft: 12, - paddingRight: 8, - flexDirection: 'row', - alignItems: 'center', - }, - message: { - flex: 1, - color: '#991B1B', - fontSize: 14, - lineHeight: 20, - }, - closeButton: { - padding: 4, - marginLeft: 8, - }, - closeText: { - color: '#991B1B', - fontSize: 16, - fontWeight: '600', - }, -}); - -function App() { - const [userInput, setUserInput] = useState(''); - const [isTextInputFocused, setIsTextInputFocused] = useState(false); - const [error, setError] = useState(null); - const textInputRef = useRef(null); - const scrollViewRef = useRef(null); - - const llm = useLLM({ - model: models.llm.lfm2_5_1_2b_instruct(), - }); - // Alternatively, to use a custom local model, uncomment below: - // const llm = useLLM({ model: { - // modelSource: require('./assets/ai-models/smolLm2/smolLm2_135M/smolLm2_135M_bf16.pte'), - // tokenizerSource: require('./assets/ai-models/smolLm2/tokenizer.json'), - // tokenizerConfigSource: require('./assets/ai-models/smolLm2/tokenizer_config.json'), - // } }); - - useEffect(() => { - if (llm.error) setError(String(llm.error)); - }, [llm.error]); - - const sendMessage = async () => { - if (!userInput.trim()) return; - - setUserInput(''); - textInputRef.current?.clear(); - try { - await llm.sendMessage(userInput); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - }; - - return ( - - - - - - setError(null)} /> - {llm.messageHistory.length > 0 || llm.isGenerating ? ( - - scrollViewRef.current?.scrollToEnd({ animated: true }) - } - keyboardShouldPersistTaps="handled" - > - {llm.messageHistory.map((message, index) => ( - - - {message.content} - - - ))} - {llm.isGenerating && llm.response && ( - - {llm.response} - - - )} - - ) : ( - - - Hello! 👋 - - What can I help you with? - - - {[ - 'Explain quantum computing in simple terms', - 'Write a short poem about coding', - 'What are the benefits of on-device AI?', - 'Give me 3 fun facts about space', - ].map((prompt, i) => ( - setUserInput(prompt)} - > - {prompt} - - ))} - - - - )} - - - setIsTextInputFocused(true)} - onBlur={() => setIsTextInputFocused(false)} - onChangeText={setUserInput} - value={userInput} - /> - {userInput.trim() && !llm.isGenerating && ( - - - Send - - - )} - {llm.isGenerating && ( - - - Stop - - - )} - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: ColorPalette.white, - }, - content: { - flex: 1, - }, - chatContainer: { - flex: 1, - width: '100%', - }, - chatContent: { - padding: 16, - flexGrow: 1, - }, - welcomeContainer: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - padding: 20, - }, - welcomeTitle: { - fontSize: 32, - fontWeight: 'bold', - color: ColorPalette.primary, - marginBottom: 12, - }, - welcomeSubtitle: { - fontSize: 18, - color: ColorPalette.blueDark, - textAlign: 'center', - }, - suggestionsContainer: { - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'center', - gap: 8, - paddingTop: 16, - }, - suggestionChip: { - borderWidth: 1, - borderColor: ColorPalette.blueLight, - borderRadius: 20, - paddingHorizontal: 14, - paddingVertical: 8, - backgroundColor: '#fafbff', - }, - suggestionChipText: { - fontSize: 13, - color: ColorPalette.primary, - }, - messageBubble: { - maxWidth: '80%', - padding: 12, - borderRadius: 16, - marginBottom: 8, - }, - userMessage: { - alignSelf: 'flex-end', - backgroundColor: ColorPalette.primary, - }, - aiMessage: { - alignSelf: 'flex-start', - backgroundColor: ColorPalette.gray100, - borderWidth: 1, - borderColor: ColorPalette.gray200, - }, - messageText: { - fontSize: 15, - lineHeight: 20, - }, - userMessageText: { - color: ColorPalette.white, - }, - aiMessageText: { - color: ColorPalette.primary, - }, - inputContainer: { - flexDirection: 'row', - padding: 16, - borderTopWidth: 1, - borderTopColor: ColorPalette.gray200, - alignItems: 'flex-end', - backgroundColor: ColorPalette.white, - }, - textInput: { - flex: 1, - borderWidth: 1, - borderRadius: 20, - paddingHorizontal: 16, - paddingVertical: 10, - fontSize: 15, - color: ColorPalette.primary, - maxHeight: 100, - marginRight: 8, - }, - sendButton: { - backgroundColor: ColorPalette.primary, - paddingHorizontal: 20, - paddingVertical: 10, - borderRadius: 20, - minWidth: 70, - alignItems: 'center', - justifyContent: 'center', - }, - sendButtonText: { - color: ColorPalette.white, - fontSize: 16, - fontWeight: '600', - }, -}); - -export default App; diff --git a/apps/bare-rn/Gemfile b/apps/bare-rn/Gemfile deleted file mode 100644 index c717940f18..0000000000 --- a/apps/bare-rn/Gemfile +++ /dev/null @@ -1,16 +0,0 @@ -source 'https://rubygems.org' - -# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version -ruby ">= 2.6.10" - -# Exclude problematic versions of cocoapods and activesupport that causes build failures. -# CocoaPods >= 1.16.0 fixes Ruby 3.4 compatibility (kconv removal). -gem 'cocoapods', '>= 1.16.2' -gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' -gem 'concurrent-ruby', '< 1.3.4' - -# Ruby 3.4.0 has removed some libraries from the standard library. -gem 'bigdecimal' -gem 'logger' -gem 'benchmark' -gem 'mutex_m' diff --git a/apps/bare-rn/Gemfile.lock b/apps/bare-rn/Gemfile.lock deleted file mode 100644 index 130a52d752..0000000000 --- a/apps/bare-rn/Gemfile.lock +++ /dev/null @@ -1,119 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.8) - activesupport (7.2.3.1) - base64 - benchmark (>= 0.3) - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - logger (>= 1.4.2) - minitest (>= 5.1, < 6) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - addressable (2.9.0) - public_suffix (>= 2.0.2, < 8.0) - algoliasearch (1.27.5) - httpclient (~> 2.8, >= 2.8.3) - json (>= 1.5.1) - atomos (0.1.3) - base64 (0.3.0) - benchmark (0.5.0) - bigdecimal (4.0.1) - claide (1.1.0) - cocoapods (1.16.2) - addressable (~> 2.8) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.16.2) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 2.1, < 3.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.6.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.8.0) - nap (~> 1.0) - ruby-macho (>= 2.3.0, < 3.0) - xcodeproj (>= 1.27.0, < 2.0) - cocoapods-core (1.16.2) - activesupport (>= 5.0, < 8) - addressable (~> 2.8) - algoliasearch (~> 1.0) - concurrent-ruby (~> 1.1) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - netrc (~> 0.11) - public_suffix (~> 4.0) - typhoeus (~> 1.0) - cocoapods-deintegrate (1.0.5) - cocoapods-downloader (2.1) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.1) - cocoapods-trunk (1.6.0) - nap (>= 0.8, < 2.0) - netrc (~> 0.11) - cocoapods-try (1.2.0) - colored2 (3.1.2) - concurrent-ruby (1.3.3) - connection_pool (3.0.2) - drb (2.2.3) - escape (0.0.4) - ethon (0.15.0) - ffi (>= 1.15.0) - ffi (1.17.3) - fourflusher (2.3.1) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - httpclient (2.9.0) - mutex_m - i18n (1.14.8) - concurrent-ruby (~> 1.0) - json (2.19.2) - logger (1.7.0) - minitest (5.27.0) - molinillo (0.8.0) - mutex_m (0.3.0) - nanaimo (0.4.0) - nap (1.1.0) - netrc (0.11.0) - public_suffix (4.0.7) - rexml (3.4.4) - ruby-macho (2.5.1) - securerandom (0.4.1) - typhoeus (1.5.0) - ethon (>= 0.9.0, < 0.16.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - xcodeproj (1.27.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.4.0) - rexml (>= 3.3.6, < 4.0) - -PLATFORMS - ruby - -DEPENDENCIES - activesupport (>= 6.1.7.5, != 7.1.0) - benchmark - bigdecimal - cocoapods (>= 1.16.2) - concurrent-ruby (< 1.3.4) - logger - mutex_m - -RUBY VERSION - ruby 3.4.5p51 - -BUNDLED WITH - 2.6.9 diff --git a/apps/bare-rn/__tests__/App.test.tsx b/apps/bare-rn/__tests__/App.test.tsx deleted file mode 100644 index e532f701ee..0000000000 --- a/apps/bare-rn/__tests__/App.test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @format - */ - -import React from 'react'; -import ReactTestRenderer from 'react-test-renderer'; -import App from '../App'; - -test('renders correctly', async () => { - await ReactTestRenderer.act(() => { - ReactTestRenderer.create(); - }); -}); diff --git a/apps/bare-rn/android/app/build.gradle b/apps/bare-rn/android/app/build.gradle deleted file mode 100644 index b17658e36c..0000000000 --- a/apps/bare-rn/android/app/build.gradle +++ /dev/null @@ -1,119 +0,0 @@ -apply plugin: "com.android.application" -apply plugin: "org.jetbrains.kotlin.android" -apply plugin: "com.facebook.react" - -/** - * This is the configuration block to customize your React Native Android app. - * By default you don't need to apply any configuration, just uncomment the lines you need. - */ -react { - /* Folders */ - // The root of your project, i.e. where "package.json" lives. Default is '../..' - root = file("../../") - // The folder where the react-native NPM package is. Default is ../../node_modules/react-native - reactNativeDir = file("../../node_modules/react-native") - // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen - codegenDir = file("../../node_modules/@react-native/codegen") - // The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js - cliFile = file("../../node_modules/react-native/cli.js") - - /* Variants */ - // The list of variants to that are debuggable. For those we're going to - // skip the bundling of the JS bundle and the assets. Default is "debug", "debugOptimized". - // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. - // debuggableVariants = ["liteDebug", "liteDebugOptimized", "prodDebug", "prodDebugOptimized"] - - /* Bundling */ - // A list containing the node command and its flags. Default is just 'node'. - // nodeExecutableAndArgs = ["node"] - // - // The command to run when bundling. By default is 'bundle' - // bundleCommand = "ram-bundle" - // - // The path to the CLI configuration file. Default is empty. - // bundleConfig = file(../rn-cli.config.js) - // - // The name of the generated asset file containing your JS bundle - // bundleAssetName = "MyApplication.android.bundle" - // - // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' - // entryFile = file("../js/MyApplication.android.js") - // - // A list of extra flags to pass to the 'bundle' commands. - // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle - // extraPackagerArgs = [] - - /* Hermes Commands */ - // The hermes compiler command to run. By default it is 'hermesc' - // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" - // - // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" - // hermesFlags = ["-O", "-output-source-map"] - - /* Autolinking */ - autolinkLibrariesWithApp() -} - -/** - * Set this to true to Run Proguard on Release builds to minify the Java bytecode. - */ -def enableProguardInReleaseBuilds = false - -/** - * The preferred build flavor of JavaScriptCore (JSC) - * - * For example, to use the international variant, you can use: - * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+` - * - * The international variant includes ICU i18n library and necessary data - * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that - * give correct results when using with locales other than en-US. Note that - * this variant is about 6MiB larger per architecture than default. - */ -def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' - -android { - ndkVersion rootProject.ext.ndkVersion - buildToolsVersion rootProject.ext.buildToolsVersion - compileSdk rootProject.ext.compileSdkVersion - - namespace "com.barern" - defaultConfig { - applicationId "com.barern" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - } - signingConfigs { - debug { - storeFile file('debug.keystore') - storePassword 'android' - keyAlias 'androiddebugkey' - keyPassword 'android' - } - } - buildTypes { - debug { - signingConfig signingConfigs.debug - } - release { - // Caution! In production, you need to generate your own keystore file. - // see https://reactnative.dev/docs/signed-apk-android. - signingConfig signingConfigs.debug - minifyEnabled enableProguardInReleaseBuilds - proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" - } - } -} - -dependencies { - // The version of react-native is set by the React Native Gradle Plugin - implementation("com.facebook.react:react-android") - - if (hermesEnabled.toBoolean()) { - implementation("com.facebook.react:hermes-android") - } else { - implementation jscFlavor - } -} diff --git a/apps/bare-rn/android/app/debug.keystore b/apps/bare-rn/android/app/debug.keystore deleted file mode 100644 index 364e105ed3..0000000000 Binary files a/apps/bare-rn/android/app/debug.keystore and /dev/null differ diff --git a/apps/bare-rn/android/app/proguard-rules.pro b/apps/bare-rn/android/app/proguard-rules.pro deleted file mode 100644 index 11b025724a..0000000000 --- a/apps/bare-rn/android/app/proguard-rules.pro +++ /dev/null @@ -1,10 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: diff --git a/apps/bare-rn/android/app/src/main/AndroidManifest.xml b/apps/bare-rn/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index fb78f39746..0000000000 --- a/apps/bare-rn/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - diff --git a/apps/bare-rn/android/app/src/main/java/com/barern/MainActivity.kt b/apps/bare-rn/android/app/src/main/java/com/barern/MainActivity.kt deleted file mode 100644 index 7ee40e43b4..0000000000 --- a/apps/bare-rn/android/app/src/main/java/com/barern/MainActivity.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.barern - -import com.facebook.react.ReactActivity -import com.facebook.react.ReactActivityDelegate -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled -import com.facebook.react.defaults.DefaultReactActivityDelegate - -class MainActivity : ReactActivity() { - /** - * Returns the name of the main component registered from JavaScript. This is used to schedule - * rendering of the component. - */ - override fun getMainComponentName(): String = "bare-rn" - - /** - * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] - * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] - */ - override fun createReactActivityDelegate(): ReactActivityDelegate = DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) -} diff --git a/apps/bare-rn/android/app/src/main/java/com/barern/MainApplication.kt b/apps/bare-rn/android/app/src/main/java/com/barern/MainApplication.kt deleted file mode 100644 index 39f296c224..0000000000 --- a/apps/bare-rn/android/app/src/main/java/com/barern/MainApplication.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.barern - -import android.app.Application -import com.barern.BuildConfig -import com.facebook.react.PackageList -import com.facebook.react.ReactApplication -import com.facebook.react.ReactHost -import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative -import com.facebook.react.ReactNativeHost -import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost -import com.facebook.react.defaults.DefaultReactNativeHost -import com.facebook.react.runtime.JSRuntimeFactory -import com.facebook.react.runtime.hermes.HermesInstance - -class MainApplication : - Application(), - ReactApplication { - @Suppress("DEPRECATION") - override val reactNativeHost: ReactNativeHost by lazy { - object : DefaultReactNativeHost(this) { - override fun getPackages() = - PackageList(this).packages.apply { - // Packages that cannot be autolinked yet can be added manually here - } - - override fun getJSMainModuleName() = "index" - - override fun getUseDeveloperSupport() = BuildConfig.DEBUG - - override val isNewArchEnabled: Boolean = true - } - } - - override val reactHost: ReactHost by lazy { - getDefaultReactHost( - context = applicationContext, - packageList = - PackageList(this).packages.apply { - // Packages that cannot be autolinked yet can be added manually here - }, - jsMainModulePath = "index", - jsRuntimeFactory = HermesInstance(), - ) - } - - override fun onCreate() { - super.onCreate() - loadReactNative(this) - } -} diff --git a/apps/bare-rn/android/app/src/main/res/drawable/rn_edit_text_material.xml b/apps/bare-rn/android/app/src/main/res/drawable/rn_edit_text_material.xml deleted file mode 100644 index 5c25e728ea..0000000000 --- a/apps/bare-rn/android/app/src/main/res/drawable/rn_edit_text_material.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - diff --git a/apps/bare-rn/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/bare-rn/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a2f5908281..0000000000 Binary files a/apps/bare-rn/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/apps/bare-rn/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/apps/bare-rn/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 1b52399808..0000000000 Binary files a/apps/bare-rn/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/bare-rn/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/bare-rn/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ff10afd6e1..0000000000 Binary files a/apps/bare-rn/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/apps/bare-rn/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/apps/bare-rn/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 115a4c768a..0000000000 Binary files a/apps/bare-rn/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/bare-rn/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/bare-rn/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index dcd3cd8083..0000000000 Binary files a/apps/bare-rn/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/apps/bare-rn/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/apps/bare-rn/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 459ca609d3..0000000000 Binary files a/apps/bare-rn/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/bare-rn/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/bare-rn/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8ca12fe024..0000000000 Binary files a/apps/bare-rn/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/apps/bare-rn/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/apps/bare-rn/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 8e19b410a1..0000000000 Binary files a/apps/bare-rn/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/bare-rn/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/bare-rn/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index b824ebdd48..0000000000 Binary files a/apps/bare-rn/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/apps/bare-rn/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/apps/bare-rn/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 4c19a13c23..0000000000 Binary files a/apps/bare-rn/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/bare-rn/android/app/src/main/res/values/strings.xml b/apps/bare-rn/android/app/src/main/res/values/strings.xml deleted file mode 100644 index 6a435ace24..0000000000 --- a/apps/bare-rn/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - bare_rn - diff --git a/apps/bare-rn/android/app/src/main/res/values/styles.xml b/apps/bare-rn/android/app/src/main/res/values/styles.xml deleted file mode 100644 index 7ba83a2ad5..0000000000 --- a/apps/bare-rn/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/apps/bare-rn/android/build.gradle b/apps/bare-rn/android/build.gradle deleted file mode 100644 index dad99b022a..0000000000 --- a/apps/bare-rn/android/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -buildscript { - ext { - buildToolsVersion = "36.0.0" - minSdkVersion = 24 - compileSdkVersion = 36 - targetSdkVersion = 36 - ndkVersion = "27.1.12297006" - kotlinVersion = "2.1.20" - } - repositories { - google() - mavenCentral() - } - dependencies { - classpath("com.android.tools.build:gradle") - classpath("com.facebook.react:react-native-gradle-plugin") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") - } -} - -apply plugin: "com.facebook.react.rootproject" diff --git a/apps/bare-rn/android/gradle.properties b/apps/bare-rn/android/gradle.properties deleted file mode 100644 index 9afe61598f..0000000000 --- a/apps/bare-rn/android/gradle.properties +++ /dev/null @@ -1,44 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m -org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true - -# Use this property to specify which architecture you want to build. -# You can also override it from the CLI using -# ./gradlew -PreactNativeArchitectures=x86_64 -reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 - -# Use this property to enable support to the new architecture. -# This will allow you to use TurboModules and the Fabric render in -# your application. You should enable this flag either if you want -# to write custom TurboModules/Fabric components OR use libraries that -# are providing them. -newArchEnabled=true - -# Use this property to enable or disable the Hermes JS engine. -# If set to false, you will be using JSC instead. -hermesEnabled=true - -# Use this property to enable edge-to-edge display support. -# This allows your app to draw behind system bars for an immersive UI. -# Note: Only works with ReactActivity and should not be used with custom Activity. -edgeToEdgeEnabled=false diff --git a/apps/bare-rn/android/gradle/wrapper/gradle-wrapper.jar b/apps/bare-rn/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 8bdaf60c75..0000000000 Binary files a/apps/bare-rn/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/apps/bare-rn/android/gradle/wrapper/gradle-wrapper.properties b/apps/bare-rn/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 2a84e188b8..0000000000 --- a/apps/bare-rn/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/apps/bare-rn/android/gradlew b/apps/bare-rn/android/gradlew deleted file mode 100755 index ef07e0162b..0000000000 --- a/apps/bare-rn/android/gradlew +++ /dev/null @@ -1,251 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH="\\\"\\\"" - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/apps/bare-rn/android/gradlew.bat b/apps/bare-rn/android/gradlew.bat deleted file mode 100644 index dd2b8eedbd..0000000000 --- a/apps/bare-rn/android/gradlew.bat +++ /dev/null @@ -1,99 +0,0 @@ -@REM Copyright (c) Meta Platforms, Inc. and affiliates. -@REM -@REM This source code is licensed under the MIT license found in the -@REM LICENSE file in the root directory of this source tree. - -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH= - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/apps/bare-rn/android/settings.gradle b/apps/bare-rn/android/settings.gradle deleted file mode 100644 index 56ad61524f..0000000000 --- a/apps/bare-rn/android/settings.gradle +++ /dev/null @@ -1,11 +0,0 @@ -pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } -plugins { id("com.facebook.react.settings") } -extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> - // bare-rn pins its own React Native via installConfig.hoistingLimits in package.json, - // so its deps live in apps/bare-rn/node_modules/ rather than the workspace root. - def cliFile = new File(settings.rootDir, "../node_modules/@react-native-community/cli/build/bin.js").absolutePath - ex.autolinkLibrariesFromCommand(["node", cliFile, "config"], new File(settings.rootDir, "../")) -} -rootProject.name = 'bare_rn' -include ':app' -includeBuild('../node_modules/@react-native/gradle-plugin') diff --git a/apps/bare-rn/app.json b/apps/bare-rn/app.json deleted file mode 100644 index 0a3ed9b0a2..0000000000 --- a/apps/bare-rn/app.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "bare-rn", - "displayName": "bare-rn" -} diff --git a/apps/bare-rn/babel.config.js b/apps/bare-rn/babel.config.js deleted file mode 100644 index b243a0ffa4..0000000000 --- a/apps/bare-rn/babel.config.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - presets: ['module:@react-native/babel-preset'], - plugins: ['@babel/plugin-transform-export-namespace-from'], -}; diff --git a/apps/bare-rn/index.js b/apps/bare-rn/index.js deleted file mode 100644 index 9b73932914..0000000000 --- a/apps/bare-rn/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @format - */ - -import { AppRegistry } from 'react-native'; -import App from './App'; -import { name as appName } from './app.json'; - -AppRegistry.registerComponent(appName, () => App); diff --git a/apps/bare-rn/ios/.xcode.env b/apps/bare-rn/ios/.xcode.env deleted file mode 100644 index 3d5782c715..0000000000 --- a/apps/bare-rn/ios/.xcode.env +++ /dev/null @@ -1,11 +0,0 @@ -# This `.xcode.env` file is versioned and is used to source the environment -# used when running script phases inside Xcode. -# To customize your local environment, you can create an `.xcode.env.local` -# file that is not versioned. - -# NODE_BINARY variable contains the PATH to the node executable. -# -# Customize the NODE_BINARY variable here. -# For example, to use nvm with brew, add the following line -# . "$(brew --prefix nvm)/nvm.sh" --no-use -export NODE_BINARY=$(command -v node) diff --git a/apps/bare-rn/ios/Podfile b/apps/bare-rn/ios/Podfile deleted file mode 100644 index cdcb24c068..0000000000 --- a/apps/bare-rn/ios/Podfile +++ /dev/null @@ -1,51 +0,0 @@ -# Resolve react_native_pods.rb with node to allow for hoisting -require Pod::Executable.execute_command('node', ['-p', - 'require.resolve( - "react-native/scripts/react_native_pods.rb", - {paths: [process.argv[1]]}, - )', __dir__]).strip - -platform :ios, '17.0' -prepare_react_native_project! - -linkage = ENV['USE_FRAMEWORKS'] -if linkage != nil - Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green - use_frameworks! :linkage => linkage.to_sym -end - -target 'bare-rn' do - config = use_native_modules! - - use_react_native!( - :path => config[:reactNativePath], - # An absolute path to your application root. - :app_path => "#{Pod::Config.instance.installation_root}/.." - ) - - post_install do |installer| - react_native_post_install( - installer, - config[:reactNativePath], - :mac_catalyst_enabled => false, - # :ccache_enabled => true - ) - - # Workaround for Xcode 26.4 + Apple clang 21's stricter consteval - # enforcement breaking fmt 9.x (vendored by RCT-Folly in RN 0.81.x). - # See facebook/react-native#55601, fmtlib/fmt#4740. Forcing the fmt and - # RCT-Folly pod targets to C++17 makes fmt's `__cplusplus >= 202002L` - # gate fail, so `FMT_CONSTEVAL` resolves to empty inside fmt's cached - # clang module — consumers (Yoga, React-performancetimeline, etc.) then - # import a non-consteval module and the basic_format_string call site - # compiles. Applies to fmt/RCT-Folly only; remove once upstream lands. - installer.pods_project.targets.each do |target| - next unless target.name == 'fmt' || target.name == 'RCT-Folly' - target.build_configurations.each do |config| - config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17' - config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)'] - config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'FMT_USE_CONSTEVAL=0' - end - end - end -end diff --git a/apps/bare-rn/ios/Podfile.lock b/apps/bare-rn/ios/Podfile.lock deleted file mode 100644 index 7d7f408088..0000000000 --- a/apps/bare-rn/ios/Podfile.lock +++ /dev/null @@ -1,2758 +0,0 @@ -PODS: - - boost (1.84.0) - - DoubleConversion (1.1.6) - - fast_float (8.0.0) - - FBLazyVector (0.81.5) - - fmt (11.0.2) - - glog (0.3.5) - - hermes-engine (0.81.5): - - hermes-engine/Pre-built (= 0.81.5) - - hermes-engine/Pre-built (0.81.5) - - MMKV (2.3.0): - - MMKVCore (~> 2.3.0) - - MMKVCore (2.3.0) - - opencv-rne (4.11.0) - - RCT-Folly (2024.11.18.00): - - boost - - DoubleConversion - - fast_float (= 8.0.0) - - fmt (= 11.0.2) - - glog - - RCT-Folly/Default (= 2024.11.18.00) - - RCT-Folly/Default (2024.11.18.00): - - boost - - DoubleConversion - - fast_float (= 8.0.0) - - fmt (= 11.0.2) - - glog - - RCT-Folly/Fabric (2024.11.18.00): - - boost - - DoubleConversion - - fast_float (= 8.0.0) - - fmt (= 11.0.2) - - glog - - RCTDeprecation (0.81.5) - - RCTRequired (0.81.5) - - RCTTypeSafety (0.81.5): - - FBLazyVector (= 0.81.5) - - RCTRequired (= 0.81.5) - - React-Core (= 0.81.5) - - React (0.81.5): - - React-Core (= 0.81.5) - - React-Core/DevSupport (= 0.81.5) - - React-Core/RCTWebSocket (= 0.81.5) - - React-RCTActionSheet (= 0.81.5) - - React-RCTAnimation (= 0.81.5) - - React-RCTBlob (= 0.81.5) - - React-RCTImage (= 0.81.5) - - React-RCTLinking (= 0.81.5) - - React-RCTNetwork (= 0.81.5) - - React-RCTSettings (= 0.81.5) - - React-RCTText (= 0.81.5) - - React-RCTVibration (= 0.81.5) - - React-callinvoker (0.81.5) - - React-Core (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-Core/Default (= 0.81.5) - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-Core/CoreModulesHeaders (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-Core/Default (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-Core/DevSupport (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-Core/Default (= 0.81.5) - - React-Core/RCTWebSocket (= 0.81.5) - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-Core/RCTActionSheetHeaders (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-Core/RCTAnimationHeaders (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-Core/RCTBlobHeaders (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-Core/RCTImageHeaders (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-Core/RCTLinkingHeaders (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-Core/RCTNetworkHeaders (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-Core/RCTSettingsHeaders (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-Core/RCTTextHeaders (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-Core/RCTVibrationHeaders (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-Core/Default - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-Core/RCTWebSocket (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTDeprecation - - React-Core/Default (= 0.81.5) - - React-cxxreact - - React-featureflags - - React-hermes - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsinspectorcdp - - React-jsitooling - - React-perflogger - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-CoreModules (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - RCTTypeSafety (= 0.81.5) - - React-Core/CoreModulesHeaders (= 0.81.5) - - React-jsi (= 0.81.5) - - React-jsinspector - - React-jsinspectorcdp - - React-jsinspectortracing - - React-NativeModulesApple - - React-RCTBlob - - React-RCTFBReactNativeSpec - - React-RCTImage (= 0.81.5) - - React-runtimeexecutor - - ReactCommon - - SocketRocket - - React-cxxreact (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-callinvoker (= 0.81.5) - - React-debug (= 0.81.5) - - React-jsi (= 0.81.5) - - React-jsinspector - - React-jsinspectorcdp - - React-jsinspectortracing - - React-logger (= 0.81.5) - - React-perflogger (= 0.81.5) - - React-runtimeexecutor - - React-timing (= 0.81.5) - - SocketRocket - - React-debug (0.81.5) - - React-defaultsnativemodule (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-domnativemodule - - React-featureflagsnativemodule - - React-idlecallbacksnativemodule - - React-jsi - - React-jsiexecutor - - React-microtasksnativemodule - - React-RCTFBReactNativeSpec - - SocketRocket - - React-domnativemodule (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-Fabric - - React-Fabric/bridging - - React-FabricComponents - - React-graphics - - React-jsi - - React-jsiexecutor - - React-RCTFBReactNativeSpec - - React-runtimeexecutor - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-Fabric (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric/animations (= 0.81.5) - - React-Fabric/attributedstring (= 0.81.5) - - React-Fabric/bridging (= 0.81.5) - - React-Fabric/componentregistry (= 0.81.5) - - React-Fabric/componentregistrynative (= 0.81.5) - - React-Fabric/components (= 0.81.5) - - React-Fabric/consistency (= 0.81.5) - - React-Fabric/core (= 0.81.5) - - React-Fabric/dom (= 0.81.5) - - React-Fabric/imagemanager (= 0.81.5) - - React-Fabric/leakchecker (= 0.81.5) - - React-Fabric/mounting (= 0.81.5) - - React-Fabric/observers (= 0.81.5) - - React-Fabric/scheduler (= 0.81.5) - - React-Fabric/telemetry (= 0.81.5) - - React-Fabric/templateprocessor (= 0.81.5) - - React-Fabric/uimanager (= 0.81.5) - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/animations (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/attributedstring (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/bridging (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/componentregistry (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/componentregistrynative (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/components (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric/components/legacyviewmanagerinterop (= 0.81.5) - - React-Fabric/components/root (= 0.81.5) - - React-Fabric/components/scrollview (= 0.81.5) - - React-Fabric/components/view (= 0.81.5) - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/components/legacyviewmanagerinterop (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/components/root (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/components/scrollview (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/components/view (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-renderercss - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-Fabric/consistency (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/core (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/dom (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/imagemanager (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/leakchecker (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/mounting (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/observers (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric/observers/events (= 0.81.5) - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/observers/events (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/scheduler (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric/observers/events - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-performancetimeline - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/telemetry (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/templateprocessor (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/uimanager (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric/uimanager/consistency (= 0.81.5) - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererconsistency - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-Fabric/uimanager/consistency (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-rendererconsistency - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - React-FabricComponents (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-FabricComponents/components (= 0.81.5) - - React-FabricComponents/textlayoutmanager (= 0.81.5) - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricComponents/components (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-FabricComponents/components/inputaccessory (= 0.81.5) - - React-FabricComponents/components/iostextinput (= 0.81.5) - - React-FabricComponents/components/modal (= 0.81.5) - - React-FabricComponents/components/rncore (= 0.81.5) - - React-FabricComponents/components/safeareaview (= 0.81.5) - - React-FabricComponents/components/scrollview (= 0.81.5) - - React-FabricComponents/components/switch (= 0.81.5) - - React-FabricComponents/components/text (= 0.81.5) - - React-FabricComponents/components/textinput (= 0.81.5) - - React-FabricComponents/components/unimplementedview (= 0.81.5) - - React-FabricComponents/components/virtualview (= 0.81.5) - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricComponents/components/inputaccessory (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricComponents/components/iostextinput (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricComponents/components/modal (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricComponents/components/rncore (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricComponents/components/safeareaview (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricComponents/components/scrollview (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricComponents/components/switch (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricComponents/components/text (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricComponents/components/textinput (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricComponents/components/unimplementedview (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricComponents/components/virtualview (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricComponents/textlayoutmanager (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-cxxreact - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-logger - - React-RCTFBReactNativeSpec - - React-rendererdebug - - React-runtimescheduler - - React-utils - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-FabricImage (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired (= 0.81.5) - - RCTTypeSafety (= 0.81.5) - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsi - - React-jsiexecutor (= 0.81.5) - - React-logger - - React-rendererdebug - - React-utils - - ReactCommon - - SocketRocket - - Yoga - - React-featureflags (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket - - React-featureflagsnativemodule (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-featureflags - - React-jsi - - React-jsiexecutor - - React-RCTFBReactNativeSpec - - ReactCommon/turbomodule/core - - SocketRocket - - React-graphics (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-jsi - - React-jsiexecutor - - React-utils - - SocketRocket - - React-hermes (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-cxxreact (= 0.81.5) - - React-jsi - - React-jsiexecutor (= 0.81.5) - - React-jsinspector - - React-jsinspectorcdp - - React-jsinspectortracing - - React-perflogger (= 0.81.5) - - React-runtimeexecutor - - SocketRocket - - React-idlecallbacksnativemodule (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-jsi - - React-jsiexecutor - - React-RCTFBReactNativeSpec - - React-runtimeexecutor - - React-runtimescheduler - - ReactCommon/turbomodule/core - - SocketRocket - - React-ImageManager (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - React-Core/Default - - React-debug - - React-Fabric - - React-graphics - - React-rendererdebug - - React-utils - - SocketRocket - - React-jserrorhandler (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-cxxreact - - React-debug - - React-featureflags - - React-jsi - - ReactCommon/turbomodule/bridging - - SocketRocket - - React-jsi (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket - - React-jsiexecutor (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-cxxreact (= 0.81.5) - - React-jsi (= 0.81.5) - - React-jsinspector - - React-jsinspectorcdp - - React-jsinspectortracing - - React-perflogger (= 0.81.5) - - React-runtimeexecutor - - SocketRocket - - React-jsinspector (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-featureflags - - React-jsi - - React-jsinspectorcdp - - React-jsinspectornetwork - - React-jsinspectortracing - - React-oscompat - - React-perflogger (= 0.81.5) - - React-runtimeexecutor - - SocketRocket - - React-jsinspectorcdp (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket - - React-jsinspectornetwork (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - React-featureflags - - React-jsinspectorcdp - - React-performancetimeline - - React-timing - - SocketRocket - - React-jsinspectortracing (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - React-oscompat - - React-timing - - SocketRocket - - React-jsitooling (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - React-cxxreact (= 0.81.5) - - React-jsi (= 0.81.5) - - React-jsinspector - - React-jsinspectorcdp - - React-jsinspectortracing - - React-runtimeexecutor - - SocketRocket - - React-jsitracing (0.81.5): - - React-jsi - - React-logger (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket - - React-Mapbuffer (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - React-debug - - SocketRocket - - React-microtasksnativemodule (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-jsi - - React-jsiexecutor - - React-RCTFBReactNativeSpec - - ReactCommon/turbomodule/core - - SocketRocket - - react-native-background-downloader (4.5.2): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - MMKV - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsi - - React-NativeModulesApple - - React-RCTFabric - - React-renderercss - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - react-native-executorch (0.9.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - opencv-rne (~> 4.11.0) - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsi - - React-NativeModulesApple - - React-RCTFabric - - React-renderercss - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - react-native-safe-area-context (5.7.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsi - - react-native-safe-area-context/common (= 5.7.0) - - react-native-safe-area-context/fabric (= 5.7.0) - - React-NativeModulesApple - - React-RCTFabric - - React-renderercss - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - react-native-safe-area-context/common (5.7.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsi - - React-NativeModulesApple - - React-RCTFabric - - React-renderercss - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - react-native-safe-area-context/fabric (5.7.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsi - - react-native-safe-area-context/common - - React-NativeModulesApple - - React-RCTFabric - - React-renderercss - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - React-NativeModulesApple (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-callinvoker - - React-Core - - React-cxxreact - - React-featureflags - - React-jsi - - React-jsinspector - - React-jsinspectorcdp - - React-runtimeexecutor - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - SocketRocket - - React-oscompat (0.81.5) - - React-perflogger (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket - - React-performancetimeline (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - React-featureflags - - React-jsinspectortracing - - React-perflogger - - React-timing - - SocketRocket - - React-RCTActionSheet (0.81.5): - - React-Core/RCTActionSheetHeaders (= 0.81.5) - - React-RCTAnimation (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - RCTTypeSafety - - React-Core/RCTAnimationHeaders - - React-featureflags - - React-jsi - - React-NativeModulesApple - - React-RCTFBReactNativeSpec - - ReactCommon - - SocketRocket - - React-RCTAppDelegate (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-CoreModules - - React-debug - - React-defaultsnativemodule - - React-Fabric - - React-featureflags - - React-graphics - - React-hermes - - React-jsitooling - - React-NativeModulesApple - - React-RCTFabric - - React-RCTFBReactNativeSpec - - React-RCTImage - - React-RCTNetwork - - React-RCTRuntime - - React-rendererdebug - - React-RuntimeApple - - React-RuntimeCore - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - ReactCommon - - SocketRocket - - React-RCTBlob (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-Core/RCTBlobHeaders - - React-Core/RCTWebSocket - - React-jsi - - React-jsinspector - - React-jsinspectorcdp - - React-NativeModulesApple - - React-RCTFBReactNativeSpec - - React-RCTNetwork - - ReactCommon - - SocketRocket - - React-RCTFabric (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-Core - - React-debug - - React-Fabric - - React-FabricComponents - - React-FabricImage - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsi - - React-jsinspector - - React-jsinspectorcdp - - React-jsinspectornetwork - - React-jsinspectortracing - - React-performancetimeline - - React-RCTAnimation - - React-RCTFBReactNativeSpec - - React-RCTImage - - React-RCTText - - React-rendererconsistency - - React-renderercss - - React-rendererdebug - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - Yoga - - React-RCTFBReactNativeSpec (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-jsi - - React-NativeModulesApple - - React-RCTFBReactNativeSpec/components (= 0.81.5) - - ReactCommon - - SocketRocket - - React-RCTFBReactNativeSpec/components (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-jsi - - React-NativeModulesApple - - React-rendererdebug - - React-utils - - ReactCommon - - SocketRocket - - Yoga - - React-RCTImage (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - RCTTypeSafety - - React-Core/RCTImageHeaders - - React-jsi - - React-NativeModulesApple - - React-RCTFBReactNativeSpec - - React-RCTNetwork - - ReactCommon - - SocketRocket - - React-RCTLinking (0.81.5): - - React-Core/RCTLinkingHeaders (= 0.81.5) - - React-jsi (= 0.81.5) - - React-NativeModulesApple - - React-RCTFBReactNativeSpec - - ReactCommon - - ReactCommon/turbomodule/core (= 0.81.5) - - React-RCTNetwork (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - RCTTypeSafety - - React-Core/RCTNetworkHeaders - - React-featureflags - - React-jsi - - React-jsinspectorcdp - - React-jsinspectornetwork - - React-NativeModulesApple - - React-RCTFBReactNativeSpec - - ReactCommon - - SocketRocket - - React-RCTRuntime (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-Core - - React-jsi - - React-jsinspector - - React-jsinspectorcdp - - React-jsinspectortracing - - React-jsitooling - - React-RuntimeApple - - React-RuntimeCore - - React-runtimeexecutor - - React-RuntimeHermes - - SocketRocket - - React-RCTSettings (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - RCTTypeSafety - - React-Core/RCTSettingsHeaders - - React-jsi - - React-NativeModulesApple - - React-RCTFBReactNativeSpec - - ReactCommon - - SocketRocket - - React-RCTText (0.81.5): - - React-Core/RCTTextHeaders (= 0.81.5) - - Yoga - - React-RCTVibration (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - React-Core/RCTVibrationHeaders - - React-jsi - - React-NativeModulesApple - - React-RCTFBReactNativeSpec - - ReactCommon - - SocketRocket - - React-rendererconsistency (0.81.5) - - React-renderercss (0.81.5): - - React-debug - - React-utils - - React-rendererdebug (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - React-debug - - SocketRocket - - React-RuntimeApple (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-callinvoker - - React-Core/Default - - React-CoreModules - - React-cxxreact - - React-featureflags - - React-jserrorhandler - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsitooling - - React-Mapbuffer - - React-NativeModulesApple - - React-RCTFabric - - React-RCTFBReactNativeSpec - - React-RuntimeCore - - React-runtimeexecutor - - React-RuntimeHermes - - React-runtimescheduler - - React-utils - - SocketRocket - - React-RuntimeCore (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-cxxreact - - React-Fabric - - React-featureflags - - React-jserrorhandler - - React-jsi - - React-jsiexecutor - - React-jsinspector - - React-jsitooling - - React-performancetimeline - - React-runtimeexecutor - - React-runtimescheduler - - React-utils - - SocketRocket - - React-runtimeexecutor (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - React-debug - - React-featureflags - - React-jsi (= 0.81.5) - - React-utils - - SocketRocket - - React-RuntimeHermes (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-featureflags - - React-hermes - - React-jsi - - React-jsinspector - - React-jsinspectorcdp - - React-jsinspectortracing - - React-jsitooling - - React-jsitracing - - React-RuntimeCore - - React-runtimeexecutor - - React-utils - - SocketRocket - - React-runtimescheduler (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-callinvoker - - React-cxxreact - - React-debug - - React-featureflags - - React-jsi - - React-jsinspectortracing - - React-performancetimeline - - React-rendererconsistency - - React-rendererdebug - - React-runtimeexecutor - - React-timing - - React-utils - - SocketRocket - - React-timing (0.81.5): - - React-debug - - React-utils (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-debug - - React-jsi (= 0.81.5) - - SocketRocket - - ReactAppDependencyProvider (0.81.5): - - ReactCodegen - - ReactCodegen (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-FabricImage - - React-featureflags - - React-graphics - - React-jsi - - React-jsiexecutor - - React-NativeModulesApple - - React-RCTAppDelegate - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - SocketRocket - - ReactCommon (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - ReactCommon/turbomodule (= 0.81.5) - - SocketRocket - - ReactCommon/turbomodule (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-callinvoker (= 0.81.5) - - React-cxxreact (= 0.81.5) - - React-jsi (= 0.81.5) - - React-logger (= 0.81.5) - - React-perflogger (= 0.81.5) - - ReactCommon/turbomodule/bridging (= 0.81.5) - - ReactCommon/turbomodule/core (= 0.81.5) - - SocketRocket - - ReactCommon/turbomodule/bridging (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-callinvoker (= 0.81.5) - - React-cxxreact (= 0.81.5) - - React-jsi (= 0.81.5) - - React-logger (= 0.81.5) - - React-perflogger (= 0.81.5) - - SocketRocket - - ReactCommon/turbomodule/core (0.81.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - React-callinvoker (= 0.81.5) - - React-cxxreact (= 0.81.5) - - React-debug (= 0.81.5) - - React-featureflags (= 0.81.5) - - React-jsi (= 0.81.5) - - React-logger (= 0.81.5) - - React-perflogger (= 0.81.5) - - React-utils (= 0.81.5) - - SocketRocket - - ReactNativeFs (2.37.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-jsi - - React-NativeModulesApple - - React-RCTFabric - - React-renderercss - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - SocketRocket (0.7.1) - - Yoga (0.0.0) - -DEPENDENCIES: - - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - - fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`) - - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) - - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) - - RCTRequired (from `../node_modules/react-native/Libraries/Required`) - - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - - React (from `../node_modules/react-native/`) - - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) - - React-Core (from `../node_modules/react-native/`) - - React-Core/RCTWebSocket (from `../node_modules/react-native/`) - - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) - - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) - - React-debug (from `../node_modules/react-native/ReactCommon/react/debug`) - - React-defaultsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) - - React-domnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/dom`) - - React-Fabric (from `../node_modules/react-native/ReactCommon`) - - React-FabricComponents (from `../node_modules/react-native/ReactCommon`) - - React-FabricImage (from `../node_modules/react-native/ReactCommon`) - - React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`) - - React-featureflagsnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) - - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) - - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) - - React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) - - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) - - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) - - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) - - React-jsinspectorcdp (from `../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) - - React-jsinspectornetwork (from `../node_modules/react-native/ReactCommon/jsinspector-modern/network`) - - React-jsinspectortracing (from `../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) - - React-jsitooling (from `../node_modules/react-native/ReactCommon/jsitooling`) - - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) - - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) - - "react-native-background-downloader (from `../node_modules/@kesha-antonov/react-native-background-downloader`)" - - react-native-executorch (from `../node_modules/react-native-executorch`) - - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`) - - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) - - React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`) - - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) - - React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`) - - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) - - React-RCTFabric (from `../node_modules/react-native/React`) - - React-RCTFBReactNativeSpec (from `../node_modules/react-native/React`) - - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) - - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) - - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) - - React-RCTRuntime (from `../node_modules/react-native/React/Runtime`) - - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) - - React-RCTText (from `../node_modules/react-native/Libraries/Text`) - - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - - React-rendererconsistency (from `../node_modules/react-native/ReactCommon/react/renderer/consistency`) - - React-renderercss (from `../node_modules/react-native/ReactCommon/react/renderer/css`) - - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) - - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) - - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - - React-timing (from `../node_modules/react-native/ReactCommon/react/timing`) - - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - - ReactAppDependencyProvider (from `build/generated/ios`) - - ReactCodegen (from `build/generated/ios`) - - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - - "ReactNativeFs (from `../node_modules/@dr.pogodin/react-native-fs`)" - - SocketRocket (~> 0.7.1) - - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) - -SPEC REPOS: - trunk: - - MMKV - - MMKVCore - - opencv-rne - - SocketRocket - -EXTERNAL SOURCES: - boost: - :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" - DoubleConversion: - :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" - fast_float: - :podspec: "../node_modules/react-native/third-party-podspecs/fast_float.podspec" - FBLazyVector: - :path: "../node_modules/react-native/Libraries/FBLazyVector" - fmt: - :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec" - glog: - :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" - hermes-engine: - :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" - :tag: hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782 - RCT-Folly: - :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" - RCTDeprecation: - :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" - RCTRequired: - :path: "../node_modules/react-native/Libraries/Required" - RCTTypeSafety: - :path: "../node_modules/react-native/Libraries/TypeSafety" - React: - :path: "../node_modules/react-native/" - React-callinvoker: - :path: "../node_modules/react-native/ReactCommon/callinvoker" - React-Core: - :path: "../node_modules/react-native/" - React-CoreModules: - :path: "../node_modules/react-native/React/CoreModules" - React-cxxreact: - :path: "../node_modules/react-native/ReactCommon/cxxreact" - React-debug: - :path: "../node_modules/react-native/ReactCommon/react/debug" - React-defaultsnativemodule: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/defaults" - React-domnativemodule: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/dom" - React-Fabric: - :path: "../node_modules/react-native/ReactCommon" - React-FabricComponents: - :path: "../node_modules/react-native/ReactCommon" - React-FabricImage: - :path: "../node_modules/react-native/ReactCommon" - React-featureflags: - :path: "../node_modules/react-native/ReactCommon/react/featureflags" - React-featureflagsnativemodule: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" - React-graphics: - :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" - React-hermes: - :path: "../node_modules/react-native/ReactCommon/hermes" - React-idlecallbacksnativemodule: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" - React-ImageManager: - :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" - React-jserrorhandler: - :path: "../node_modules/react-native/ReactCommon/jserrorhandler" - React-jsi: - :path: "../node_modules/react-native/ReactCommon/jsi" - React-jsiexecutor: - :path: "../node_modules/react-native/ReactCommon/jsiexecutor" - React-jsinspector: - :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" - React-jsinspectorcdp: - :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" - React-jsinspectornetwork: - :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/network" - React-jsinspectortracing: - :path: "../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" - React-jsitooling: - :path: "../node_modules/react-native/ReactCommon/jsitooling" - React-jsitracing: - :path: "../node_modules/react-native/ReactCommon/hermes/executor/" - React-logger: - :path: "../node_modules/react-native/ReactCommon/logger" - React-Mapbuffer: - :path: "../node_modules/react-native/ReactCommon" - React-microtasksnativemodule: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" - react-native-background-downloader: - :path: "../node_modules/@kesha-antonov/react-native-background-downloader" - react-native-executorch: - :path: "../node_modules/react-native-executorch" - react-native-safe-area-context: - :path: "../node_modules/react-native-safe-area-context" - React-NativeModulesApple: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" - React-oscompat: - :path: "../node_modules/react-native/ReactCommon/oscompat" - React-perflogger: - :path: "../node_modules/react-native/ReactCommon/reactperflogger" - React-performancetimeline: - :path: "../node_modules/react-native/ReactCommon/react/performance/timeline" - React-RCTActionSheet: - :path: "../node_modules/react-native/Libraries/ActionSheetIOS" - React-RCTAnimation: - :path: "../node_modules/react-native/Libraries/NativeAnimation" - React-RCTAppDelegate: - :path: "../node_modules/react-native/Libraries/AppDelegate" - React-RCTBlob: - :path: "../node_modules/react-native/Libraries/Blob" - React-RCTFabric: - :path: "../node_modules/react-native/React" - React-RCTFBReactNativeSpec: - :path: "../node_modules/react-native/React" - React-RCTImage: - :path: "../node_modules/react-native/Libraries/Image" - React-RCTLinking: - :path: "../node_modules/react-native/Libraries/LinkingIOS" - React-RCTNetwork: - :path: "../node_modules/react-native/Libraries/Network" - React-RCTRuntime: - :path: "../node_modules/react-native/React/Runtime" - React-RCTSettings: - :path: "../node_modules/react-native/Libraries/Settings" - React-RCTText: - :path: "../node_modules/react-native/Libraries/Text" - React-RCTVibration: - :path: "../node_modules/react-native/Libraries/Vibration" - React-rendererconsistency: - :path: "../node_modules/react-native/ReactCommon/react/renderer/consistency" - React-renderercss: - :path: "../node_modules/react-native/ReactCommon/react/renderer/css" - React-rendererdebug: - :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" - React-RuntimeApple: - :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" - React-RuntimeCore: - :path: "../node_modules/react-native/ReactCommon/react/runtime" - React-runtimeexecutor: - :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" - React-RuntimeHermes: - :path: "../node_modules/react-native/ReactCommon/react/runtime" - React-runtimescheduler: - :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" - React-timing: - :path: "../node_modules/react-native/ReactCommon/react/timing" - React-utils: - :path: "../node_modules/react-native/ReactCommon/react/utils" - ReactAppDependencyProvider: - :path: build/generated/ios - ReactCodegen: - :path: build/generated/ios - ReactCommon: - :path: "../node_modules/react-native/ReactCommon" - ReactNativeFs: - :path: "../node_modules/@dr.pogodin/react-native-fs" - Yoga: - :path: "../node_modules/react-native/ReactCommon/yoga" - -SPEC CHECKSUMS: - boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb - fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 - FBLazyVector: 5beb8028d5a2e75dd9634917f23e23d3a061d2aa - fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd - glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 - hermes-engine: 9f4dfe93326146a1c99eb535b1cb0b857a3cd172 - MMKV: c953dbaac0da392c24b005e763c03ce2638b4ed7 - MMKVCore: d078dce7d6586a888b2c2ef5343b6242678e3ee8 - opencv-rne: 2305807573b6e29c8c87e3416ab096d09047a7a0 - RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 - RCTDeprecation: 5eb1d2eeff5fb91151e8a8eef45b6c7658b6c897 - RCTRequired: cebcf9442fc296c9b89ac791dfd463021d9f6f23 - RCTTypeSafety: b99aa872829ee18f6e777e0ef55852521c5a6788 - React: 914f8695f9bf38e6418228c2ffb70021e559f92f - React-callinvoker: 23cd4e33928608bd0cc35357597568b8b9a5f068 - React-Core: 6a0a97598e9455348113bfe4c573fe8edac34469 - React-CoreModules: a88a6ca48b668401b9780e272e2a607e70f9f955 - React-cxxreact: 06265fd7e8d5c3b6b49e00d328ef76e5f1ae9c8b - React-debug: 039d3dbd3078613e02e3960439bbf52f6d321bc4 - React-defaultsnativemodule: 09efbfa17b15445907689c577e371558d8b08135 - React-domnativemodule: 6284a09207d8e0e974affb0d84b43a0c1aee2554 - React-Fabric: 5ffa7f2a10fb3bf835f97990d341419ae338963d - React-FabricComponents: 25173bc205a6b7c18d87121891f3acef1c329b04 - React-FabricImage: aa90e4b2b34a79f9b4ee56328ad9222cb672f1f3 - React-featureflags: 7bdaca8af1def3ec9203743c91b11ac7c2cb2574 - React-featureflagsnativemodule: 6840bc359820d5e44f1de1f9ba69706e0a88a60b - React-graphics: b0a76138e325f9c5dfcc8fbc62491ab252ca736c - React-hermes: a852be3ab9e1f515e46ba3ea9f48c31d4a9df437 - React-idlecallbacksnativemodule: 38895fd946b2dcb0af387f2176f5f2e578b14277 - React-ImageManager: 44409a10adff7091c9e678b97ee59c7b0576b8ae - React-jserrorhandler: 3852205bbfc68277cd4e7392ad1fa74a170150fd - React-jsi: 7b53959aea60909ac6bbe4dd0bdec6c10d7dc597 - React-jsiexecutor: 19938072af05ade148474bac41e0324a2d733f44 - React-jsinspector: 0aecd79939adf576c6dd7bbbddf90b630e7827e4 - React-jsinspectorcdp: 8245973529c78d150aebddd2c497ee290349faf0 - React-jsinspectornetwork: 496a12dbc80835fac10acf29b9c4386ddcc472f1 - React-jsinspectortracing: 1939b3e0cec087983384c5561bf925f35287d760 - React-jsitooling: 86c70336d5c371b4289499e9303b6da118ad3eeb - React-jsitracing: 8eb0d50d7874886fb3ec7f85e0567f1964a20075 - React-logger: a913317214a26565cd4c045347edf1bcacb80a3f - React-Mapbuffer: 94f4264de2cb156960cd82b338a403f4653f2fd9 - React-microtasksnativemodule: 6c4ee39a36958c39c97b074d28f360246a335e84 - react-native-background-downloader: 114f96122822fa97b06ea0f2250b8e8270696995 - react-native-executorch: 273c1ffd131a92738c35267c7af737275e8b32a5 - react-native-safe-area-context: befb5404eb8a16fdc07fa2bebab3568ecabcbb8a - React-NativeModulesApple: ebf2ce72b35870036900d6498b33724386540a71 - React-oscompat: eb0626e8ba1a2c61673c991bf9dc21834898475d - React-perflogger: 509e1f9a3ee28df71b0a66de806ac515ce951246 - React-performancetimeline: 43a1ea36ac47853b479ae85e04c1c339721e99f1 - React-RCTActionSheet: 30fe8f9f8d86db4a25ff34595a658ecd837485fc - React-RCTAnimation: 3126eb1cb8e7a6ca33a52fd833d8018aa9311af1 - React-RCTAppDelegate: b03981c790aa40cf26e0f78cc0f1f2df8287ead4 - React-RCTBlob: 53c35e85c85d6bdaa55dc81a0b290d4e78431095 - React-RCTFabric: 4e2a4176f99b6b8f2d2eda9fc82453a3e6c3ef8e - React-RCTFBReactNativeSpec: 947126c649e04b95457a40bc97c4b2a76206534b - React-RCTImage: 074b2faa71a152a456c974e118b60c9eeda94a64 - React-RCTLinking: e5ca17a4f7ae2ad7b0c0483be77e1b383ecd0a8a - React-RCTNetwork: c508d7548c9eceac30a8100a846ea00033a03366 - React-RCTRuntime: 6813778046c775c124179d9e4d7b33d4129bbd84 - React-RCTSettings: dd84c857a4fce42c1e08c1dabcda894e25af4a6e - React-RCTText: 6e4b177d047f98bccb90d6fb1ebdd3391cf8b299 - React-RCTVibration: 9572d4a06a0c92650bcc62913e50eb2a89f19fb6 - React-rendererconsistency: 6f0622076d7b26eda57a582db5ffd8b05fe94652 - React-renderercss: c00b6db35f01e2f17e496d1d0793fc0be89d4f7b - React-rendererdebug: 17f707ba5ba1ed7a10dd997a2e27b2431b24a180 - React-RuntimeApple: b9b9a53afd594eb49c3e6891f84327d1834a2c5e - React-RuntimeCore: 5a0c78665206a44c4a030e2b4af0c8d6ad05ae77 - React-runtimeexecutor: 7f56789cd23bd4ea1f95595eb5c27e08cee3a19e - React-RuntimeHermes: b2d6bc03f4cc9d2eb7ee0a1bfe16c226cb2114ce - React-runtimescheduler: 5cc5c0568bf216e1ee8f3c2c0a1cff2ef3091b32 - React-timing: b1e27e61bd184fab3792947685bebdb2dc55af9a - React-utils: ddf52534853a3b5f19d4615b1a1f172b504673f2 - ReactAppDependencyProvider: 1bcd3527ac0390a1c898c114f81ff954be35ed79 - ReactCodegen: 6c26f8c25d0b5ae66f86a1cce1777076ac8bcbd8 - ReactCommon: 5f0e5c09a64a2717215dd84380e1a747810406f2 - ReactNativeFs: 5fc447c96bcf90d038b79695d721628fc7bb0170 - SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 728df40394d49f3f471688747cf558158b3a3bd1 - -PODFILE CHECKSUM: 421dcdee6cbb1e2a032c3b2e3ffc9371849b2199 - -COCOAPODS: 1.16.2 diff --git a/apps/bare-rn/ios/bare-rn.xcodeproj/project.pbxproj b/apps/bare-rn/ios/bare-rn.xcodeproj/project.pbxproj deleted file mode 100644 index 2a603318fc..0000000000 --- a/apps/bare-rn/ios/bare-rn.xcodeproj/project.pbxproj +++ /dev/null @@ -1,482 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 0AF1DC9B7BF72575F4599DF4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; - 0C80B921A6F3F58F76C31292 /* libPods-bare-rn.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-bare-rn.a */; }; - 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; - 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; - 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 13B07F961A680F5B00A75B9A /* bare-rn.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "bare-rn.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = "bare-rn/Images.xcassets"; sourceTree = ""; }; - 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = "bare-rn/Info.plist"; sourceTree = ""; }; - 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = "bare-rn/PrivacyInfo.xcprivacy"; sourceTree = ""; }; - 3B4392A12AC88292D35C810B /* Pods-bare-rn.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-bare-rn.debug.xcconfig"; path = "Target Support Files/Pods-bare-rn/Pods-bare-rn.debug.xcconfig"; sourceTree = ""; }; - 5709B34CF0A7D63546082F79 /* Pods-bare-rn.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-bare-rn.release.xcconfig"; path = "Target Support Files/Pods-bare-rn/Pods-bare-rn.release.xcconfig"; sourceTree = ""; }; - 5DCACB8F33CDC322A6C60F78 /* libPods-bare-rn.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-bare-rn.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = "bare-rn/AppDelegate.swift"; sourceTree = ""; }; - 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = "bare-rn/LaunchScreen.storyboard"; sourceTree = ""; }; - ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 0C80B921A6F3F58F76C31292 /* libPods-bare-rn.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 13B07FAE1A68108700A75B9A /* bare-rn */ = { - isa = PBXGroup; - children = ( - 13B07FB51A68108700A75B9A /* Images.xcassets */, - 761780EC2CA45674006654EE /* AppDelegate.swift */, - 13B07FB61A68108700A75B9A /* Info.plist */, - 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, - 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, - ); - name = "bare-rn"; - sourceTree = ""; - }; - 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { - isa = PBXGroup; - children = ( - ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 5DCACB8F33CDC322A6C60F78 /* libPods-bare-rn.a */, - ); - name = Frameworks; - sourceTree = ""; - }; - 832341AE1AAA6A7D00B99B32 /* Libraries */ = { - isa = PBXGroup; - children = ( - ); - name = Libraries; - sourceTree = ""; - }; - 83CBB9F61A601CBA00E9B192 = { - isa = PBXGroup; - children = ( - 13B07FAE1A68108700A75B9A /* bare-rn */, - 832341AE1AAA6A7D00B99B32 /* Libraries */, - 83CBBA001A601CBA00E9B192 /* Products */, - 2D16E6871FA4F8E400B85C8A /* Frameworks */, - BBD78D7AC51CEA395F1C20DB /* Pods */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - usesTabs = 0; - }; - 83CBBA001A601CBA00E9B192 /* Products */ = { - isa = PBXGroup; - children = ( - 13B07F961A680F5B00A75B9A /* bare-rn.app */, - ); - name = Products; - sourceTree = ""; - }; - BBD78D7AC51CEA395F1C20DB /* Pods */ = { - isa = PBXGroup; - children = ( - 3B4392A12AC88292D35C810B /* Pods-bare-rn.debug.xcconfig */, - 5709B34CF0A7D63546082F79 /* Pods-bare-rn.release.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 13B07F861A680F5B00A75B9A /* bare-rn */ = { - isa = PBXNativeTarget; - buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "bare-rn" */; - buildPhases = ( - C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */, - 13B07F871A680F5B00A75B9A /* Sources */, - 13B07F8C1A680F5B00A75B9A /* Frameworks */, - 13B07F8E1A680F5B00A75B9A /* Resources */, - 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */, - E235C05ADACE081382539298 /* [CP] Copy Pods Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "bare-rn"; - productName = "bare-rn"; - productReference = 13B07F961A680F5B00A75B9A /* bare-rn.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 83CBB9F71A601CBA00E9B192 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1210; - TargetAttributes = { - 13B07F861A680F5B00A75B9A = { - LastSwiftMigration = 1120; - }; - }; - }; - buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "bare-rn" */; - compatibilityVersion = "Xcode 12.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 83CBB9F61A601CBA00E9B192; - productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 13B07F861A680F5B00A75B9A /* bare-rn */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 13B07F8E1A680F5B00A75B9A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, - 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, - 0AF1DC9B7BF72575F4599DF4 /* PrivacyInfo.xcprivacy in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(SRCROOT)/.xcode.env.local", - "$(SRCROOT)/.xcode.env", - ); - name = "Bundle React Native code and images"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"\\\"$WITH_ENVIRONMENT\\\" \\\"$REACT_NATIVE_XCODE\\\"\"\n"; - }; - 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-bare-rn/Pods-bare-rn-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-bare-rn/Pods-bare-rn-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-bare-rn/Pods-bare-rn-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-bare-rn-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-bare-rn/Pods-bare-rn-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-bare-rn/Pods-bare-rn-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-bare-rn/Pods-bare-rn-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 13B07F871A680F5B00A75B9A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 13B07F941A680F5B00A75B9A /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-bare-rn.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 1; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = "bare-rn/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - OTHER_LDFLAGS = ( - "$(inherited)", - "-ObjC", - "-lc++", - ); - PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "bare-rn"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 13B07F951A680F5B00A75B9A /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-bare-rn.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 1; - INFOPLIST_FILE = "bare-rn/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - OTHER_LDFLAGS = ( - "$(inherited)", - "-ObjC", - "-lc++", - ); - PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "bare-rn"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; - 83CBBA201A601CBA00E9B192 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++20"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - LD_RUNPATH_SEARCH_PATHS = ( - /usr/lib/swift, - "$(inherited)", - ); - LIBRARY_SEARCH_PATHS = ( - "\"$(SDKROOT)/usr/lib/swift\"", - "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", - "\"$(inherited)\"", - ); - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - OTHER_CPLUSPLUSFLAGS = ( - "$(OTHER_CFLAGS)", - "-DFOLLY_NO_CONFIG", - "-DFOLLY_MOBILE=1", - "-DFOLLY_USE_LIBCPP=1", - "-DFOLLY_CFG_NO_COROUTINES=1", - "-DFOLLY_HAVE_CLOCK_GETTIME=1", - ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; - USE_HERMES = true; - }; - name = Debug; - }; - 83CBBA211A601CBA00E9B192 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++20"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = YES; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - LD_RUNPATH_SEARCH_PATHS = ( - /usr/lib/swift, - "$(inherited)", - ); - LIBRARY_SEARCH_PATHS = ( - "\"$(SDKROOT)/usr/lib/swift\"", - "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", - "\"$(inherited)\"", - ); - MTL_ENABLE_DEBUG_INFO = NO; - OTHER_CPLUSPLUSFLAGS = ( - "$(OTHER_CFLAGS)", - "-DFOLLY_NO_CONFIG", - "-DFOLLY_MOBILE=1", - "-DFOLLY_USE_LIBCPP=1", - "-DFOLLY_CFG_NO_COROUTINES=1", - "-DFOLLY_HAVE_CLOCK_GETTIME=1", - ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; - SDKROOT = iphoneos; - USE_HERMES = true; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "bare-rn" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 13B07F941A680F5B00A75B9A /* Debug */, - 13B07F951A680F5B00A75B9A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "bare-rn" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 83CBBA201A601CBA00E9B192 /* Debug */, - 83CBBA211A601CBA00E9B192 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; -} diff --git a/apps/bare-rn/ios/bare-rn.xcodeproj/xcshareddata/xcschemes/bare-rn.xcscheme b/apps/bare-rn/ios/bare-rn.xcodeproj/xcshareddata/xcschemes/bare-rn.xcscheme deleted file mode 100644 index 6106d33b22..0000000000 --- a/apps/bare-rn/ios/bare-rn.xcodeproj/xcshareddata/xcschemes/bare-rn.xcscheme +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/bare-rn/ios/bare-rn.xcworkspace/contents.xcworkspacedata b/apps/bare-rn/ios/bare-rn.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 190b831198..0000000000 --- a/apps/bare-rn/ios/bare-rn.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/apps/bare-rn/ios/bare-rn/AppDelegate.swift b/apps/bare-rn/ios/bare-rn/AppDelegate.swift deleted file mode 100644 index 4fb4ca4833..0000000000 --- a/apps/bare-rn/ios/bare-rn/AppDelegate.swift +++ /dev/null @@ -1,48 +0,0 @@ -import UIKit -import React -import React_RCTAppDelegate -import ReactAppDependencyProvider - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - - var reactNativeDelegate: ReactNativeDelegate? - var reactNativeFactory: RCTReactNativeFactory? - - func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil - ) -> Bool { - let delegate = ReactNativeDelegate() - let factory = RCTReactNativeFactory(delegate: delegate) - delegate.dependencyProvider = RCTAppDependencyProvider() - - reactNativeDelegate = delegate - reactNativeFactory = factory - - window = UIWindow(frame: UIScreen.main.bounds) - - factory.startReactNative( - withModuleName: "bare-rn", - in: window, - launchOptions: launchOptions - ) - - return true - } -} - -class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { - override func sourceURL(for bridge: RCTBridge) -> URL? { - self.bundleURL() - } - - override func bundleURL() -> URL? { -#if DEBUG - RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") -#else - Bundle.main.url(forResource: "main", withExtension: "jsbundle") -#endif - } -} diff --git a/apps/bare-rn/ios/bare-rn/Images.xcassets/AppIcon.appiconset/Contents.json b/apps/bare-rn/ios/bare-rn/Images.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index ddd7fca89e..0000000000 --- a/apps/bare-rn/ios/bare-rn/Images.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "images": [ - { - "idiom": "iphone", - "scale": "2x", - "size": "20x20" - }, - { - "idiom": "iphone", - "scale": "3x", - "size": "20x20" - }, - { - "idiom": "iphone", - "scale": "2x", - "size": "29x29" - }, - { - "idiom": "iphone", - "scale": "3x", - "size": "29x29" - }, - { - "idiom": "iphone", - "scale": "2x", - "size": "40x40" - }, - { - "idiom": "iphone", - "scale": "3x", - "size": "40x40" - }, - { - "idiom": "iphone", - "scale": "2x", - "size": "60x60" - }, - { - "idiom": "iphone", - "scale": "3x", - "size": "60x60" - }, - { - "idiom": "ios-marketing", - "scale": "1x", - "size": "1024x1024" - } - ], - "info": { - "author": "xcode", - "version": 1 - } -} diff --git a/apps/bare-rn/ios/bare-rn/Images.xcassets/Contents.json b/apps/bare-rn/ios/bare-rn/Images.xcassets/Contents.json deleted file mode 100644 index 97a8662ebd..0000000000 --- a/apps/bare-rn/ios/bare-rn/Images.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info": { - "version": 1, - "author": "xcode" - } -} diff --git a/apps/bare-rn/ios/bare-rn/Info.plist b/apps/bare-rn/ios/bare-rn/Info.plist deleted file mode 100644 index 37fc9b5e77..0000000000 --- a/apps/bare-rn/ios/bare-rn/Info.plist +++ /dev/null @@ -1,60 +0,0 @@ - - - - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - bare-rn - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - LSRequiresIPhoneOS - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - NSAllowsLocalNetworking - - - NSLocationWhenInUseUsageDescription - - RCTNewArchEnabled - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - arm64 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - UIViewControllerBasedStatusBarAppearance - - - diff --git a/apps/bare-rn/ios/bare-rn/LaunchScreen.storyboard b/apps/bare-rn/ios/bare-rn/LaunchScreen.storyboard deleted file mode 100644 index 4bc47247dc..0000000000 --- a/apps/bare-rn/ios/bare-rn/LaunchScreen.storyboard +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/bare-rn/ios/bare-rn/PrivacyInfo.xcprivacy b/apps/bare-rn/ios/bare-rn/PrivacyInfo.xcprivacy deleted file mode 100644 index 41da7af967..0000000000 --- a/apps/bare-rn/ios/bare-rn/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,47 +0,0 @@ - - - - - NSPrivacyAccessedAPITypes - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryFileTimestamp - NSPrivacyAccessedAPITypeReasons - - C617.1 - 0A2A.1 - - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryUserDefaults - NSPrivacyAccessedAPITypeReasons - - CA92.1 - - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryDiskSpace - NSPrivacyAccessedAPITypeReasons - - 85F4.1 - E174.1 - - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategorySystemBootTime - NSPrivacyAccessedAPITypeReasons - - 35F9.1 - - - - NSPrivacyCollectedDataTypes - - NSPrivacyTracking - - - diff --git a/apps/bare-rn/jest.config.js b/apps/bare-rn/jest.config.js deleted file mode 100644 index 8eb675e9bc..0000000000 --- a/apps/bare-rn/jest.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - preset: 'react-native', -}; diff --git a/apps/bare-rn/metro.config.js b/apps/bare-rn/metro.config.js deleted file mode 100644 index facaedca93..0000000000 --- a/apps/bare-rn/metro.config.js +++ /dev/null @@ -1,30 +0,0 @@ -const path = require('path'); -const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); - -const workspaceRoot = path.resolve(__dirname, '../../'); // Adjust the path to your monorepo root -const projectRoot = __dirname; -const defaultConfig = getDefaultConfig(projectRoot); - -const config = { - watchFolders: [ - workspaceRoot, // Watch the entire monorepo - ], - resolver: { - // Workspace packages (react-native-executorch, bare-resource-fetcher) declare - // their own `react` devDependency, so yarn installs a second React copy at - // packages/*/node_modules/react. Without disabling hierarchical lookup, Metro - // resolves `react` per-file and the bundle ends up with two React instances — - // useState's dispatcher comes back null at runtime. - disableHierarchicalLookup: true, - nodeModulesPaths: [ - path.resolve(projectRoot, 'node_modules'), - path.resolve(workspaceRoot, 'node_modules'), - ], - assetExts: [ - ...defaultConfig.resolver.assetExts, - 'pte', // ExecuTorch model files - ], - }, -}; - -module.exports = mergeConfig(defaultConfig, config); diff --git a/apps/bare-rn/package.json b/apps/bare-rn/package.json deleted file mode 100644 index e9201874bd..0000000000 --- a/apps/bare-rn/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "bare-rn", - "version": "0.0.1", - "private": true, - "scripts": { - "android": "react-native run-android", - "ios": "react-native run-ios", - "typecheck": "tsc", - "lint": "eslint . --ext .ts,.tsx --fix", - "start": "react-native start", - "test": "jest" - }, - "dependencies": { - "@dr.pogodin/react-native-fs": "^2.36.2", - "@kesha-antonov/react-native-background-downloader": "^4.4.5", - "react": "19.1.0", - "react-native": "0.81.5", - "react-native-executorch": "workspace:*", - "react-native-executorch-bare-resource-fetcher": "workspace:*", - "react-native-safe-area-context": "^5.5.2" - }, - "devDependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-transform-export-namespace-from": "^7.25.2", - "@babel/preset-env": "^7.25.3", - "@babel/runtime": "^7.25.0", - "@react-native-community/cli": "20.0.0", - "@react-native-community/cli-platform-android": "20.0.0", - "@react-native-community/cli-platform-ios": "20.0.0", - "@react-native/babel-preset": "0.81.5", - "@react-native/eslint-config": "0.81.5", - "@react-native/metro-config": "0.81.5", - "@react-native/typescript-config": "0.81.5", - "@types/jest": "^29.5.13", - "@types/react": "^19.1.0", - "@types/react-test-renderer": "^19.1.0", - "eslint": "^8.19.0", - "jest": "^29.6.3", - "prettier": "2.8.8", - "react-test-renderer": "19.1.0", - "typescript": "^5.8.3" - }, - "engines": { - "node": ">=22" - }, - "installConfig": { - "hoistingLimits": "workspaces" - } -} diff --git a/apps/bare-rn/tsconfig.json b/apps/bare-rn/tsconfig.json deleted file mode 100644 index 4fe6f139ef..0000000000 --- a/apps/bare-rn/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "jsx": "react-native", - "lib": ["es2022"], - "moduleResolution": "bundler", - "noEmit": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "strict": true, - "target": "esnext" - }, - "include": ["**/*.ts", "**/*.tsx"], - "exclude": ["**/node_modules", "**/Pods", "**/__tests__"] -} diff --git a/apps/computer-vision/.gitignore b/apps/computer-vision/.gitignore deleted file mode 100644 index f779c900a7..0000000000 --- a/apps/computer-vision/.gitignore +++ /dev/null @@ -1,38 +0,0 @@ -# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files - -# dependencies -node_modules/ - -# Expo -.expo/ -dist/ -web-build/ -expo-env.d.ts - -# Native -*.orig.* -*.jks -*.p8 -*.p12 -*.key -*.mobileprovision - -# Metro -.metro-health-check* - -# debug -npm-debug.* -yarn-debug.* -yarn-error.* - -# macOS -.DS_Store -*.pem - -# local env files -.env*.local - -# typescript -*.tsbuildinfo - -.yarn diff --git a/apps/computer-vision/ScreenWrapper.tsx b/apps/computer-vision/ScreenWrapper.tsx deleted file mode 100644 index 31f70e4442..0000000000 --- a/apps/computer-vision/ScreenWrapper.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { useIsFocused } from 'expo-router'; -import { PropsWithChildren } from 'react'; - -export default function ScreenWrapper({ children }: PropsWithChildren) { - const isFocused = useIsFocused(); - - return isFocused ? <>{children} : null; -} diff --git a/apps/computer-vision/app.json b/apps/computer-vision/app.json deleted file mode 100644 index 6aef6ff728..0000000000 --- a/apps/computer-vision/app.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "expo": { - "name": "computer-vision", - "slug": "computer-vision", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/icons/icon.png", - "userInterfaceStyle": "light", - "newArchEnabled": true, - "scheme": "rne-computer-vision", - "splash": { - "image": "./assets/icons/splash.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, - "ios": { - "supportsTablet": true, - "bundleIdentifier": "com.anonymous.computervision", - "infoPlist": { - "NSCameraUsageDescription": "Process photo from camera" - } - }, - "android": { - "adaptiveIcon": { - "foregroundImage": "./assets/icons/adaptive-icon.png", - "backgroundColor": "#ffffff" - }, - "package": "com.anonymous.computervision", - "permissions": ["android.permission.CAMERA"] - }, - "web": { - "favicon": "./assets/icons/favicon.png" - }, - "plugins": [ - "expo-font", - "expo-router", - [ - "expo-build-properties", - { - "android": { - "minSdkVersion": 26 - }, - "ios": { - "deploymentTarget": "17.0" - } - } - ] - ] - } -} diff --git a/apps/computer-vision/app/_layout.tsx b/apps/computer-vision/app/_layout.tsx deleted file mode 100644 index 5d5c67dca1..0000000000 --- a/apps/computer-vision/app/_layout.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import { Drawer } from 'expo-router/drawer'; -import { initExecutorch } from 'react-native-executorch'; -import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher'; - -import ColorPalette from '../colors'; -import React, { useState } from 'react'; -import { - Text, - StyleSheet, - View, - TouchableOpacity, - type ColorValue, -} from 'react-native'; - -import { - type DrawerContentComponentProps, - DrawerContentScrollView, - DrawerItemList, -} from 'expo-router/build/react-navigation/drawer'; -import { DrawerActions } from 'expo-router/build/react-navigation/routers'; -import { useNavigation } from 'expo-router'; -import Svg, { Rect } from 'react-native-svg'; -import { GeneratingContext } from '../context'; - -initExecutorch({ - resourceFetcher: ExpoResourceFetcher, -}); - -function HamburgerIcon({ tintColor }: { tintColor?: ColorValue }) { - const navigation = useNavigation(); - const fill = typeof tintColor === 'string' ? tintColor : '#000'; - return ( - navigation.dispatch(DrawerActions.toggleDrawer())} - style={styles.hamburger} - > - - - - - - - ); -} - -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 }, - headerLeft: (props) => , - }} - > - null, - title: 'Main Menu', - drawerItemStyle: { display: 'none' }, - }} - /> - - - - - - - - - - - - - - ); -} - -const styles = StyleSheet.create({ - hamburger: { - marginLeft: 12, - }, - 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/computer-vision/app/classification/index.tsx b/apps/computer-vision/app/classification/index.tsx deleted file mode 100644 index 55a42f7422..0000000000 --- a/apps/computer-vision/app/classification/index.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import Spinner from '../../components/Spinner'; -import { getImage } from '../../utils'; -import { - models, - useClassification, - ClassificationModelSources, -} from 'react-native-executorch'; -import { ModelPicker, ModelOption } from '../../components/ModelPicker'; -const classification = models.classification; - -const MODELS: ModelOption[] = [ - { - label: 'EfficientNet V2 S Quantized', - value: classification.efficientnet_v2_s(), - }, - { - label: 'EfficientNet V2 S', - value: classification.efficientnet_v2_s({ quant: false }), - }, -]; -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'; -import { StatsBar } from '../../components/StatsBar'; -import ErrorBanner from '../../components/ErrorBanner'; - -export default function ClassificationScreen() { - const [selectedModel, setSelectedModel] = - useState(classification.efficientnet_v2_s()); - const [results, setResults] = useState<{ label: string; score: number }[]>( - [] - ); - const [imageUri, setImageUri] = useState(''); - const [inferenceTime, setInferenceTime] = useState(null); - - const [error, setError] = useState(null); - - const model = useClassification({ model: selectedModel }); - const { setGlobalGenerating } = useContext(GeneratingContext); - - useEffect(() => { - setGlobalGenerating(model.isGenerating); - }, [model.isGenerating, setGlobalGenerating]); - - useEffect(() => { - if (model.error) setError(String(model.error)); - }, [model.error]); - - const handleCameraPress = async (isCamera: boolean) => { - const image = await getImage(isCamera); - const uri = image?.uri; - if (typeof uri === 'string') { - setImageUri(uri as string); - setResults([]); - setInferenceTime(null); - } - }; - - const runForward = async () => { - if (imageUri) { - try { - const start = Date.now(); - const output = await model.forward(imageUri); - setInferenceTime(Date.now() - start); - const top10 = Object.entries(output) - .sort(([, a], [, b]) => (b as number) - (a as number)) - .slice(0, 10) - .map(([label, score]) => ({ label, score: score as number })); - setResults(top10); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - } - }; - - if (!model.isReady && !model.error) { - return ( - - ); - } - - return ( - - setError(null)} /> - - - - {!imageUri && ( - - Image Classification - - This model analyzes an image and returns the top 10 most likely - labels with confidence scores. Use the gallery or camera icons - below to pick an image, then tap the button to run the model. - - - )} - {results.length > 0 && ( - - Results Top 10 - - {results.map(({ label, score }) => ( - - {label} - {score.toFixed(3)} - - ))} - - - )} - - { - setSelectedModel(m); - setResults([]); - }} - /> - - - - ); -} - -const styles = StyleSheet.create({ - imageContainer: { - flex: 6, - width: '100%', - padding: 16, - }, - image: { - flex: 2, - borderRadius: 8, - width: '100%', - }, - results: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - gap: 4, - padding: 4, - }, - resultHeader: { - fontSize: 18, - color: 'navy', - }, - resultsList: { - flex: 1, - }, - resultRecord: { - flexDirection: 'row', - width: '100%', - justifyContent: 'space-between', - padding: 8, - borderBottomWidth: 1, - }, - resultLabel: { - flex: 1, - marginRight: 4, - }, - infoContainer: { - alignItems: 'center', - padding: 16, - gap: 8, - }, - infoTitle: { - fontSize: 18, - fontWeight: '600', - color: 'navy', - }, - infoText: { - fontSize: 14, - color: '#555', - textAlign: 'center', - lineHeight: 20, - }, -}); diff --git a/apps/computer-vision/app/index.tsx b/apps/computer-vision/app/index.tsx deleted file mode 100644 index e67e7eb5cb..0000000000 --- a/apps/computer-vision/app/index.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { useRouter } from 'expo-router'; -import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; -import ColorPalette from '../colors'; -import ExecutorchLogo from '../assets/icons/executorch.svg'; - -export default function Home() { - const router = useRouter(); - - return ( - - - Select a demo model - - router.navigate('vision_camera/')} - > - Vision Camera - - router.navigate('classification/')} - > - Classification - - router.navigate('semantic_segmentation/')} - > - Semantic Segmentation - - router.navigate('object_detection/')} - > - Object Detection - - router.navigate('instance_segmentation/')} - > - Instance Segmentation - - router.navigate('pose_estimation/')} - > - Pose Estimation - - router.navigate('segment_anything/')} - > - Segment Anything - - router.navigate('ocr/')} - > - OCR - - router.navigate('ocr_vertical/')} - > - OCR Vertical - - router.navigate('style_transfer/')} - > - Style Transfer - - router.navigate('text_to_image/')} - > - Image Generation - - - - ); -} - -export const fontSizes = { - xxl: 34, - xl: 22, - lg: 18, - md: 16, - sm: 14, - xs: 12, - xxs: 10, -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#fff', - }, - headerText: { - fontSize: fontSizes.lg, - color: ColorPalette.strongPrimary, - margin: 20, - }, - buttonContainer: { - width: '80%', - justifyContent: 'space-evenly', - marginBottom: 20, - }, - button: { - backgroundColor: ColorPalette.strongPrimary, - borderRadius: 8, - padding: 10, - alignItems: 'center', - marginBottom: 10, - }, - buttonText: { - color: 'white', - fontSize: fontSizes.md, - }, -}); diff --git a/apps/computer-vision/app/instance_segmentation/index.tsx b/apps/computer-vision/app/instance_segmentation/index.tsx deleted file mode 100644 index fea9698eb3..0000000000 --- a/apps/computer-vision/app/instance_segmentation/index.tsx +++ /dev/null @@ -1,367 +0,0 @@ -import Spinner from '../../components/Spinner'; -import { BottomBar } from '../../components/BottomBar'; -import { getImage } from '../../utils'; -import { ModelPicker, ModelOption } from '../../components/ModelPicker'; -import { - models, - useInstanceSegmentation, - InstanceSegmentationModelSources, -} from 'react-native-executorch'; -import { - View, - StyleSheet, - ScrollView, - Text, - TouchableOpacity, -} from 'react-native'; -import React, { useContext, useEffect, useState } from 'react'; -import { GeneratingContext } from '../../context'; -import ScreenWrapper from '../../ScreenWrapper'; -import ImageWithMasks, { - buildDisplayInstances, - DisplayInstance, -} from '../../components/ImageWithMasks'; -import { StatsBar } from '../../components/StatsBar'; -const instanceSegmentation = models.instance_segmentation; - -const MODELS: ModelOption[] = [ - { label: 'Yolo26N', value: instanceSegmentation.yolo26n() }, - { label: 'Yolo26S', value: instanceSegmentation.yolo26s() }, - { label: 'Yolo26M', value: instanceSegmentation.yolo26m() }, - { label: 'Yolo26L', value: instanceSegmentation.yolo26l() }, - { label: 'Yolo26X', value: instanceSegmentation.yolo26x() }, - { - label: 'RF-DeTR Nano', - value: instanceSegmentation.rf_detr_nano(), - }, - { label: 'FastSAM-S', value: instanceSegmentation.fastsam_s() }, - { label: 'FastSAM-X', value: instanceSegmentation.fastsam_x() }, -]; - -export default function InstanceSegmentationScreen() { - const [selectedModel, setSelectedModel] = - useState(instanceSegmentation.yolo26n()); - const [inferenceTime, setInferenceTime] = useState(null); - - const { setGlobalGenerating } = useContext(GeneratingContext); - - const { - isReady, - isGenerating, - downloadProgress, - forward, - error, - getAvailableInputSizes, - } = useInstanceSegmentation({ - model: selectedModel, - }); - - const [imageUri, setImageUri] = useState(''); - const [imageSize, setImageSize] = useState({ width: 0, height: 0 }); - const [instances, setInstances] = useState([]); - const [selectedInputSize, setSelectedInputSize] = useState( - null - ); - - const availableInputSizes = getAvailableInputSizes(); - - useEffect(() => { - setGlobalGenerating(isGenerating); - }, [isGenerating, setGlobalGenerating]); - - // Set default input size when model is ready - useEffect(() => { - if (!isReady) return; - - if (availableInputSizes && availableInputSizes.length > 0) { - setSelectedInputSize((prev) => { - if (typeof prev === 'number' && availableInputSizes.includes(prev)) { - return prev; - } - return availableInputSizes[0]; - }); - return; - } - - setSelectedInputSize(null); - }, [isReady, availableInputSizes]); - - const handleCameraPress = async (isCamera: boolean) => { - const image = await getImage(isCamera); - if (!image?.uri) return; - setImageUri(image.uri); - setImageSize({ - width: image.width ?? 0, - height: image.height ?? 0, - }); - setInstances([]); - setInferenceTime(null); - }; - - const runForward = async () => { - if (!imageUri || imageSize.width === 0 || imageSize.height === 0) return; - - const inputSize = - availableInputSizes && - typeof selectedInputSize === 'number' && - availableInputSizes.includes(selectedInputSize) - ? selectedInputSize - : undefined; - - try { - const start = Date.now(); - const output = await forward(imageUri, { - confidenceThreshold: 0.5, - iouThreshold: 0.55, - maxInstances: 20, - returnMaskAtOriginalResolution: true, - inputSize, - }); - - setInferenceTime(Date.now() - start); - - // Convert raw masks → small Skia images immediately. - // Raw Uint8Array mask buffers (backed by native OwningArrayBuffer) - // go out of scope here and become eligible for GC right away. - setInstances(buildDisplayInstances(output)); - } catch (e) { - console.error(e); - } - }; - - if (!isReady && error) { - return ( - - - Error Loading Model - - {error?.message || 'Unknown error occurred'} - - Code: {error?.code || 'N/A'} - - - ); - } - - if (!isReady) { - return ( - - ); - } - - return ( - - - - - {!imageUri && ( - - Instance Segmentation - - This model detects individual objects and draws a precise mask - over each one. Pick an image from your gallery or take one with - your camera to get started. - - - )} - - - {imageUri && availableInputSizes && availableInputSizes.length > 0 && ( - - Input Size: - - {availableInputSizes.map((size) => ( - setSelectedInputSize(size)} - > - - {size} - - - ))} - - - )} - - {instances.length > 0 && ( - - - Detected {instances.length} instance(s) - - - {instances.map((instance, idx) => ( - - - {instance.label || 'Unknown'} ( - {(instance.score * 100).toFixed(1)}%) - - - ))} - - - )} - - - { - setSelectedModel(m); - setInstances([]); - setInferenceTime(null); - }} - /> - - 0 ? instances.length : null} - /> - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 6, - width: '100%', - }, - imageContainer: { - flex: 1, - width: '100%', - padding: 16, - }, - inputSizeContainer: { - paddingHorizontal: 16, - paddingVertical: 12, - backgroundColor: '#fff', - borderTopWidth: 1, - borderTopColor: '#e0e0e0', - }, - inputSizeLabel: { - fontSize: 14, - fontWeight: '600', - color: '#333', - marginBottom: 8, - }, - inputSizeScroll: { - flexDirection: 'row', - }, - sizeButton: { - paddingHorizontal: 16, - paddingVertical: 8, - marginRight: 8, - borderRadius: 6, - backgroundColor: '#f0f0f0', - }, - sizeButtonActive: { - backgroundColor: '#007AFF', - }, - sizeButtonText: { - fontSize: 14, - fontWeight: '600', - color: '#666', - }, - sizeButtonTextActive: { - color: '#fff', - }, - resultsContainer: { - maxHeight: 200, - paddingHorizontal: 16, - paddingVertical: 12, - backgroundColor: '#fff', - borderTopWidth: 1, - borderTopColor: '#e0e0e0', - }, - resultsHeader: { - fontSize: 16, - fontWeight: '600', - marginBottom: 8, - color: '#333', - }, - resultsList: { - flex: 1, - }, - resultRow: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: 6, - paddingHorizontal: 8, - marginBottom: 4, - backgroundColor: '#f9f9f9', - borderRadius: 6, - }, - resultText: { - fontSize: 14, - fontWeight: '500', - color: '#333', - }, - errorContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - padding: 32, - }, - errorTitle: { - fontSize: 20, - fontWeight: '700', - color: '#e74c3c', - marginBottom: 12, - }, - errorText: { - fontSize: 14, - color: '#555', - textAlign: 'center', - marginBottom: 8, - }, - errorCode: { - fontSize: 12, - color: '#999', - fontFamily: 'Courier', - }, - infoContainer: { - alignItems: 'center', - padding: 16, - gap: 8, - }, - infoTitle: { - fontSize: 18, - fontWeight: '600', - color: 'navy', - }, - infoText: { - fontSize: 14, - color: '#555', - textAlign: 'center', - lineHeight: 20, - }, -}); diff --git a/apps/computer-vision/app/object_detection/index.tsx b/apps/computer-vision/app/object_detection/index.tsx deleted file mode 100644 index 1ed3c136ba..0000000000 --- a/apps/computer-vision/app/object_detection/index.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import Spinner from '../../components/Spinner'; -import { BottomBar } from '../../components/BottomBar'; -import { ModelPicker, ModelOption } from '../../components/ModelPicker'; -import { getImage } from '../../utils'; -import { - models, - Detection, - useObjectDetection, - ObjectDetectionModelSources, -} from 'react-native-executorch'; -import { View, StyleSheet, Image, Text } from 'react-native'; -import ImageWithBboxes from '../../components/ImageWithBboxes'; -import React, { useContext, useEffect, useState } from 'react'; -import { GeneratingContext } from '../../context'; -import ScreenWrapper from '../../ScreenWrapper'; -import { StatsBar } from '../../components/StatsBar'; -const objectDetection = models.object_detection; - -const MODELS: ModelOption[] = [ - { - label: 'RF-DeTR Nano', - value: objectDetection.rf_detr_nano(), - }, - { - label: 'SSDLite MobileNet', - value: objectDetection.ssdlite_320_mobilenet_v3_large(), - }, - { label: 'YOLO26N', value: objectDetection.yolo26n() }, - { label: 'YOLO26S', value: objectDetection.yolo26s() }, - { label: 'YOLO26M', value: objectDetection.yolo26m() }, - { label: 'YOLO26L', value: objectDetection.yolo26l() }, - { label: 'YOLO26X', value: objectDetection.yolo26x() }, -]; -import ErrorBanner from '../../components/ErrorBanner'; - -export default function ObjectDetectionScreen() { - const [imageUri, setImageUri] = useState(''); - const [results, setResults] = useState([]); - const [error, setError] = useState(null); - const [imageDimensions, setImageDimensions] = useState<{ - width: number; - height: number; - }>(); - const [selectedModel, setSelectedModel] = - useState(objectDetection.rf_detr_nano()); - const [inferenceTime, setInferenceTime] = useState(null); - - const model = useObjectDetection({ model: selectedModel }); - const { setGlobalGenerating } = useContext(GeneratingContext); - useEffect(() => { - setGlobalGenerating(model.isGenerating); - }, [model.isGenerating, setGlobalGenerating]); - - useEffect(() => { - if (model.error) setError(String(model.error)); - }, [model.error]); - - const handleCameraPress = async (isCamera: boolean) => { - const image = await getImage(isCamera); - const uri = image?.uri; - const width = image?.width; - const height = image?.height; - - if (uri && width && height) { - setImageUri(image.uri as string); - setImageDimensions({ width: width as number, height: height as number }); - setResults([]); - setInferenceTime(null); - } - }; - - const runForward = async () => { - if (imageUri) { - try { - const start = Date.now(); - const output = await model.forward(imageUri); - setInferenceTime(Date.now() - start); - setResults(output); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - } - }; - - if (!model.isReady) { - return ( - - ); - } - - return ( - - setError(null)} /> - - - {imageUri && imageDimensions?.width && imageDimensions?.height ? ( - - ) : ( - - )} - - {!imageUri && ( - - Object Detection - - This model detects objects in an image and draws bounding boxes - around them with class labels and confidence scores. Pick an image - from your gallery or take one with your camera to get started. - - - )} - - { - setSelectedModel(m); - setResults([]); - }} - /> - 0 ? results.length : null} - /> - - - ); -} - -const styles = StyleSheet.create({ - imageContainer: { - flex: 6, - width: '100%', - padding: 16, - }, - image: { - flex: 2, - borderRadius: 8, - width: '100%', - }, - fullSizeImage: { - width: '100%', - height: '100%', - }, - infoContainer: { - alignItems: 'center', - padding: 16, - gap: 8, - }, - infoTitle: { - fontSize: 18, - fontWeight: '600', - color: 'navy', - }, - infoText: { - fontSize: 14, - color: '#555', - textAlign: 'center', - lineHeight: 20, - }, -}); diff --git a/apps/computer-vision/app/ocr/index.tsx b/apps/computer-vision/app/ocr/index.tsx deleted file mode 100644 index b81b57326c..0000000000 --- a/apps/computer-vision/app/ocr/index.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import Spinner from '../../components/Spinner'; -import { BottomBar } from '../../components/BottomBar'; -import { ModelPicker, ModelOption } from '../../components/ModelPicker'; -import { getImage } from '../../utils'; -import { models, useOCR, OCRProps } from 'react-native-executorch'; -import { View, StyleSheet, Image, Text, ScrollView } from 'react-native'; -import ImageWithBboxes2 from '../../components/ImageWithOCRBboxes'; -import React, { useContext, useEffect, useState } from 'react'; -import { GeneratingContext } from '../../context'; -import ScreenWrapper from '../../ScreenWrapper'; -import { StatsBar } from '../../components/StatsBar'; - -type OCRModelSources = OCRProps['model']; - -const ocr = models.ocr.craft; - -const MODELS: ModelOption[] = [ - { label: 'English', value: ocr({ language: 'en' }) }, - { label: 'German', value: ocr({ language: 'de' }) }, - { label: 'French', value: ocr({ language: 'fr' }) }, - { label: 'Spanish', value: ocr({ language: 'es' }) }, - { label: 'Italian', value: ocr({ language: 'it' }) }, - { label: 'Japanese', value: ocr({ language: 'ja' }) }, - { label: 'Korean', value: ocr({ language: 'ko' }) }, -]; -import ErrorBanner from '../../components/ErrorBanner'; - -export default function OCRScreen() { - const [imageUri, setImageUri] = useState(''); - const [results, setResults] = useState([]); - const [error, setError] = useState(null); - const [imageDimensions, setImageDimensions] = useState<{ - width: number; - height: number; - }>(); - const [selectedModel, setSelectedModel] = useState( - ocr({ language: 'en' }) - ); - const [inferenceTime, setInferenceTime] = useState(null); - - const model = useOCR({ - model: selectedModel, - }); - const { setGlobalGenerating } = useContext(GeneratingContext); - useEffect(() => { - setGlobalGenerating(model.isGenerating); - }, [model.isGenerating, setGlobalGenerating]); - - useEffect(() => { - if (model.error) setError(String(model.error)); - }, [model.error]); - - const handleCameraPress = async (isCamera: boolean) => { - const image = await getImage(isCamera); - const width = image?.width; - const height = image?.height; - setImageDimensions({ width: width as number, height: height as number }); - const uri = image?.uri; - if (typeof uri === 'string') { - setImageUri(uri as string); - setResults([]); - setInferenceTime(null); - } - }; - - const runForward = async () => { - try { - const start = Date.now(); - const output = await model.forward(imageUri); - setInferenceTime(Date.now() - start); - setResults(output); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - }; - - if (!model.isReady && !model.error) { - return ( - - ); - } - - return ( - - setError(null)} /> - - - {imageUri && imageDimensions?.width && imageDimensions?.height ? ( - - ) : ( - - )} - - {!imageUri && ( - - OCR - - This model reads and extracts text from images, returning each - detected text region with its bounding box and confidence score. - Pick an image from your gallery or take one with your camera to - get started. - - - )} - {results.length > 0 && ( - - Results - - {results.map(({ text, score }, index) => ( - - {text} - {score.toFixed(3)} - - ))} - - - )} - - { - setSelectedModel(m); - setResults([]); - }} - /> - 0 ? results.length : null} - /> - - - ); -} - -const styles = StyleSheet.create({ - imageContainer: { - flex: 2, - borderRadius: 8, - width: '100%', - }, - container: { - flex: 6, - width: '100%', - padding: 16, - }, - image: { - width: '100%', - height: '100%', - }, - results: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - gap: 4, - padding: 4, - }, - resultHeader: { - fontSize: 18, - color: 'navy', - }, - resultsList: { - flex: 1, - }, - resultRecord: { - flexDirection: 'row', - width: '100%', - justifyContent: 'space-between', - padding: 8, - borderBottomWidth: 1, - }, - resultLabel: { - flex: 1, - marginRight: 4, - }, - infoContainer: { - alignItems: 'center', - padding: 16, - gap: 8, - }, - infoTitle: { - fontSize: 18, - fontWeight: '600', - color: 'navy', - }, - infoText: { - fontSize: 14, - color: '#555', - textAlign: 'center', - lineHeight: 20, - }, -}); diff --git a/apps/computer-vision/app/ocr_vertical/index.tsx b/apps/computer-vision/app/ocr_vertical/index.tsx deleted file mode 100644 index f1a015eb01..0000000000 --- a/apps/computer-vision/app/ocr_vertical/index.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import Spinner from '../../components/Spinner'; -import { BottomBar } from '../../components/BottomBar'; -import { getImage } from '../../utils'; -import { models, useVerticalOCR } from 'react-native-executorch'; -import { View, StyleSheet, Image, Text, ScrollView } from 'react-native'; -import ImageWithBboxes2 from '../../components/ImageWithOCRBboxes'; -import React, { useContext, useEffect, useState } from 'react'; -import { GeneratingContext } from '../../context'; -import ScreenWrapper from '../../ScreenWrapper'; -import { StatsBar } from '../../components/StatsBar'; -import ErrorBanner from '../../components/ErrorBanner'; - -export default function VerticalOCRScreen() { - const [imageUri, setImageUri] = useState(''); - const [results, setResults] = useState([]); - const [imageDimensions, setImageDimensions] = useState<{ - width: number; - height: number; - }>(); - const [inferenceTime, setInferenceTime] = useState(null); - - const [error, setError] = useState(null); - - const model = useVerticalOCR({ - model: models.ocr.craft({ language: 'en' }), - independentCharacters: true, - }); - - const { setGlobalGenerating } = useContext(GeneratingContext); - - useEffect(() => { - setGlobalGenerating(model.isGenerating); - }, [model.isGenerating, setGlobalGenerating]); - - useEffect(() => { - if (model.error) setError(String(model.error)); - }, [model.error]); - - const handleCameraPress = async (isCamera: boolean) => { - const image = await getImage(isCamera); - const width = image?.width; - const height = image?.height; - setImageDimensions({ width: width as number, height: height as number }); - const uri = image?.uri; - if (typeof uri === 'string') { - setImageUri(uri as string); - setResults([]); - setInferenceTime(null); - } - }; - - const runForward = async () => { - try { - const start = Date.now(); - const output = await model.forward(imageUri); - setInferenceTime(Date.now() - start); - setResults(output); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - }; - - if (!model.isReady && !model.error) { - return ( - - ); - } - - return ( - - - setError(null)} /> - - - {imageUri && imageDimensions?.width && imageDimensions?.height ? ( - - ) : ( - - )} - - {!imageUri && ( - - Vertical OCR - - This model reads vertical text (e.g. Japanese, Korean, Chinese - columns) from images, returning each detected text region with its - bounding box and confidence score. Pick an image from your gallery - or take one with your camera to get started. - - - )} - {imageUri && inferenceTime !== null && results.length === 0 && ( - - No text detected - - The model did not find any vertical text in this image. Try an - image containing vertical Japanese, Korean, or Chinese text. - - - )} - {results.length > 0 && ( - - Results - - {results.map(({ text, score }, index) => ( - - {text} - {score?.toFixed(3)} - - ))} - - - )} - - 0 ? results.length : null} - /> - - - ); -} - -const styles = StyleSheet.create({ - imageContainer: { - flex: 2, - borderRadius: 8, - width: '100%', - }, - container: { - flex: 6, - width: '100%', - padding: 16, - }, - image: { - width: '100%', - height: '100%', - }, - results: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - gap: 4, - padding: 4, - }, - resultHeader: { - fontSize: 18, - color: 'navy', - }, - resultsList: { - flex: 1, - }, - resultRecord: { - flexDirection: 'row', - width: '100%', - justifyContent: 'space-between', - padding: 8, - borderBottomWidth: 1, - }, - resultLabel: { - flex: 1, - marginRight: 4, - }, - infoContainer: { - alignItems: 'center', - padding: 16, - gap: 8, - }, - infoTitle: { - fontSize: 18, - fontWeight: '600', - color: 'navy', - }, - infoText: { - fontSize: 14, - color: '#555', - textAlign: 'center', - lineHeight: 20, - }, -}); diff --git a/apps/computer-vision/app/pose_estimation/index.tsx b/apps/computer-vision/app/pose_estimation/index.tsx deleted file mode 100644 index 4546b628fa..0000000000 --- a/apps/computer-vision/app/pose_estimation/index.tsx +++ /dev/null @@ -1,259 +0,0 @@ -import Spinner from '../../components/Spinner'; -import { BottomBar } from '../../components/BottomBar'; -import { getImage } from '../../utils'; -import { - models, - usePoseEstimation, - PoseDetections, - RnExecutorchError, - RnExecutorchErrorCode, -} from 'react-native-executorch'; -import { View, StyleSheet, Image, Text } from 'react-native'; -import React, { useContext, useEffect, useState } from 'react'; -import { GeneratingContext } from '../../context'; -import ScreenWrapper from '../../ScreenWrapper'; -import { StatsBar } from '../../components/StatsBar'; -import Svg, { Circle, Line } from 'react-native-svg'; -import ErrorBanner from '../../components/ErrorBanner'; -import { COCO_SKELETON_CONNECTIONS } from '../../components/utils/cocoSkeleton'; - -// Colors for different people -const PERSON_COLORS = ['lime', 'cyan', 'magenta', 'yellow', 'orange', 'pink']; - -export default function PoseEstimationScreen() { - const [imageUri, setImageUri] = useState(''); - const [results, setResults] = useState([]); - const [error, setError] = useState(null); - const [imageDimensions, setImageDimensions] = useState<{ - width: number; - height: number; - }>(); - const [inferenceTime, setInferenceTime] = useState(null); - const [layout, setLayout] = useState({ width: 0, height: 0 }); - - const model = usePoseEstimation({ model: models.pose_estimation.yolo26n() }); - const { setGlobalGenerating } = useContext(GeneratingContext); - - useEffect(() => { - setGlobalGenerating(model.isGenerating); - }, [model.isGenerating, setGlobalGenerating]); - - useEffect(() => { - if (model.error) setError(String(model.error)); - }, [model.error]); - - const handleCameraPress = async (isCamera: boolean) => { - const image = await getImage(isCamera); - const uri = image?.uri; - const width = image?.width; - const height = image?.height; - - if (uri && width && height) { - setImageUri(image.uri as string); - setImageDimensions({ width, height }); - setResults([]); - setInferenceTime(null); - } - }; - - const runForward = async () => { - if (imageUri) { - try { - const start = Date.now(); - const output = await model.forward(imageUri, { inputSize: 384 }); - setInferenceTime(Date.now() - start); - setResults(output); - } catch (e) { - if (e instanceof RnExecutorchError) { - switch (e.code) { - case RnExecutorchErrorCode.FileReadFailed: - setError('Could not read the selected image.'); - break; - case RnExecutorchErrorCode.ModelGenerating: - setError('Model is busy — wait for the current run to finish.'); - break; - case RnExecutorchErrorCode.InvalidUserInput: - case RnExecutorchErrorCode.InvalidArgument: - setError(`Invalid input: ${e.message}`); - break; - default: - setError(e.message); - } - } else { - setError(e instanceof Error ? e.message : String(e)); - } - } - } - }; - - if (!model.isReady) { - return ( - - ); - } - - return ( - - setError(null)} /> - - - {imageUri && imageDimensions?.width && imageDimensions?.height ? ( - - setLayout({ - width: e.nativeEvent.layout.width, - height: e.nativeEvent.layout.height, - }) - } - > - - {results.length > 0 && - layout.width > 0 && - layout.height > 0 && - (() => { - // Account for resizeMode="contain" letterboxing: the image's - // displayed area is smaller than the container in one axis. - const imageRatio = - imageDimensions.width / imageDimensions.height; - const layoutRatio = layout.width / layout.height; - let scaleX: number, scaleY: number; - if (imageRatio > layoutRatio) { - scaleX = layout.width / imageDimensions.width; - scaleY = layout.width / imageRatio / imageDimensions.height; - } else { - scaleY = layout.height / imageDimensions.height; - scaleX = - (layout.height * imageRatio) / imageDimensions.width; - } - const offsetX = - (layout.width - imageDimensions.width * scaleX) / 2; - const offsetY = - (layout.height - imageDimensions.height * scaleY) / 2; - const isInBounds = (kp: { x: number; y: number }) => - kp.x >= 0 && - kp.y >= 0 && - kp.x <= imageDimensions.width && - kp.y <= imageDimensions.height; - return ( - - {results.map((personKeypoints, personIdx) => { - const color = - PERSON_COLORS[personIdx % PERSON_COLORS.length]; - return ( - - {COCO_SKELETON_CONNECTIONS.map( - ([from, to], lineIdx) => { - const kp1 = personKeypoints[from]; - const kp2 = personKeypoints[to]; - if (!kp1 || !kp2) return null; - if (!isInBounds(kp1) || !isInBounds(kp2)) - return null; - return ( - - ); - } - )} - {Object.entries(personKeypoints) - .filter(([, kp]) => isInBounds(kp)) - .map(([name, kp]) => ( - - ))} - - ); - })} - - ); - })()} - - ) : ( - - )} - - {!imageUri && ( - - Pose Estimation - - This model detects human body keypoints (17 COCO keypoints) and - draws a skeleton overlay. Pick an image from your gallery or take - one with your camera to get started. - - - )} - - 0 ? results.length : null} - /> - - - ); -} - -const styles = StyleSheet.create({ - imageContainer: { - flex: 6, - width: '100%', - padding: 16, - }, - image: { - flex: 2, - borderRadius: 8, - width: '100%', - }, - imageWrapper: { - flex: 1, - width: '100%', - height: '100%', - }, - fullSizeImage: { - width: '100%', - height: '100%', - }, - infoContainer: { - alignItems: 'center', - padding: 16, - gap: 8, - }, - infoTitle: { - fontSize: 18, - fontWeight: '600', - color: 'navy', - }, - infoText: { - fontSize: 14, - color: '#555', - textAlign: 'center', - lineHeight: 20, - }, -}); diff --git a/apps/computer-vision/app/segment_anything/index.tsx b/apps/computer-vision/app/segment_anything/index.tsx deleted file mode 100644 index ac7bbd06b5..0000000000 --- a/apps/computer-vision/app/segment_anything/index.tsx +++ /dev/null @@ -1,615 +0,0 @@ -import React, { useContext, useEffect, useRef, useState } from 'react'; -import { - View, - StyleSheet, - Text, - TextInput, - TouchableOpacity, - TouchableWithoutFeedback, - GestureResponderEvent, - Keyboard, - KeyboardAvoidingView, - Platform, -} from 'react-native'; -import { - Canvas, - Rect, - Skia, - useImage, - type SkImage, - ColorType, - AlphaType, -} from '@shopify/react-native-skia'; -import { - models, - useInstanceSegmentation, - useImageEmbeddings, - useTextEmbeddings, - InstanceSegmentationModelSources, - SegmentedInstance, - FastSAMLabel, - selectByPoint, - selectByBox, - selectByText, - Bbox, -} from 'react-native-executorch'; -import { GeneratingContext } from '../../context'; -import { ModelPicker, ModelOption } from '../../components/ModelPicker'; -import { BottomBar } from '../../components/BottomBar'; -import { StatsBar } from '../../components/StatsBar'; -import Spinner from '../../components/Spinner'; -import ScreenWrapper from '../../ScreenWrapper'; -import ImageWithMasks, { - buildDisplayInstances, - DisplayInstance, -} from '../../components/ImageWithMasks'; -import { getImage } from '../../utils'; -import ColorPalette from '../../colors'; -const instanceSegmentation = models.instance_segmentation; - -type PromptMode = 'point' | 'box' | 'text'; - -const MODELS: ModelOption[] = [ - { label: 'FastSAM-S', value: instanceSegmentation.fastsam_s() }, - { label: 'FastSAM-X', value: instanceSegmentation.fastsam_x() }, -]; - -export default function SegmentAnythingScreen() { - const { setGlobalGenerating } = useContext(GeneratingContext); - - const [selectedModel, setSelectedModel] = - useState( - instanceSegmentation.fastsam_s() - ); - const [mode, setMode] = useState('point'); - const [inferenceTime, setInferenceTime] = useState(null); - - const [imageUri, setImageUri] = useState(''); - const [imageSize, setImageSize] = useState({ width: 0, height: 0 }); - - const rawInstancesRef = useRef[]>([]); - const [selection, setSelection] = useState([]); - - const [draftBox, setDraftBox] = useState(null); - const boxStartRef = useRef<{ x: number; y: number } | null>(null); - const layoutRef = useRef({ width: 0, height: 0 }); - - const { isReady, isGenerating, downloadProgress, forward, error } = - useInstanceSegmentation({ model: selectedModel }); - - const clipImage = useImageEmbeddings({ - model: models.image_embedding.clip_vit_base_patch32_image(), - }); - const clipText = useTextEmbeddings({ - model: models.text_embedding.clip_vit_base_patch32_text(), - }); - const skiaSource = useImage(imageUri || null); - - const [textPrompt, setTextPrompt] = useState(''); - const [textBusy, setTextBusy] = useState(false); - const [embeddingProgress, setEmbeddingProgress] = useState<{ - done: number; - total: number; - } | null>(null); - const instanceEmbeddingsRef = useRef(null); - - useEffect(() => { - setGlobalGenerating(isGenerating); - }, [isGenerating, setGlobalGenerating]); - - function applyMatch( - match: SegmentedInstance | null - ): void { - setSelection(match ? buildDisplayInstances([match]) : []); - } - - function touchToImageCoords(touchX: number, touchY: number) { - const { width: cw, height: ch } = layoutRef.current; - const { width: iw, height: ih } = imageSize; - if (iw === 0 || ih === 0) return null; - const scale = Math.min(cw / iw, ch / ih); - return { - x: (touchX - (cw - iw * scale) / 2) / scale, - y: (touchY - (ch - ih * scale) / 2) / scale, - }; - } - - function handleTap(e: GestureResponderEvent) { - if (mode !== 'point' || rawInstancesRef.current.length === 0) return; - const c = touchToImageCoords( - e.nativeEvent.locationX, - e.nativeEvent.locationY - ); - if (!c) return; - applyMatch( - selectByPoint(rawInstancesRef.current, Math.round(c.x), Math.round(c.y)) - ); - } - - function handleBoxStart(e: GestureResponderEvent) { - if (mode !== 'box') return; - const c = touchToImageCoords( - e.nativeEvent.locationX, - e.nativeEvent.locationY - ); - if (!c) return; - boxStartRef.current = c; - setDraftBox({ x1: c.x, y1: c.y, x2: c.x, y2: c.y }); - } - - function handleBoxMove(e: GestureResponderEvent) { - if (mode !== 'box' || !boxStartRef.current) return; - const c = touchToImageCoords( - e.nativeEvent.locationX, - e.nativeEvent.locationY - ); - if (!c) return; - const s = boxStartRef.current; - setDraftBox({ - x1: Math.min(s.x, c.x), - y1: Math.min(s.y, c.y), - x2: Math.max(s.x, c.x), - y2: Math.max(s.y, c.y), - }); - } - - function handleBoxEnd(e: GestureResponderEvent) { - if (mode !== 'box' || !boxStartRef.current) return; - const c = touchToImageCoords( - e.nativeEvent.locationX, - e.nativeEvent.locationY - ); - const s = boxStartRef.current; - boxStartRef.current = null; - setDraftBox(null); - if (!c || rawInstancesRef.current.length === 0) return; - applyMatch( - selectByBox(rawInstancesRef.current, { - x1: Math.min(s.x, c.x), - y1: Math.min(s.y, c.y), - x2: Math.max(s.x, c.x), - y2: Math.max(s.y, c.y), - }) - ); - } - - async function runTextPrompt() { - Keyboard.dismiss(); - const instances = rawInstancesRef.current; - if ( - !textPrompt.trim() || - instances.length === 0 || - !skiaSource || - !clipImage.isReady || - !clipText.isReady || - textBusy - ) { - return; - } - setTextBusy(true); - try { - if (!instanceEmbeddingsRef.current) { - setEmbeddingProgress({ done: 0, total: instances.length }); - const embeddings: Float32Array[] = []; - for (let i = 0; i < instances.length; i++) { - const inst = instances[i]!; - embeddings.push( - await cropAndEmbed( - skiaSource, - inst.bbox, - inst.mask, - inst.maskWidth, - inst.maskHeight, - clipImage.forward - ) - ); - setEmbeddingProgress({ done: i + 1, total: instances.length }); - } - instanceEmbeddingsRef.current = embeddings; - setEmbeddingProgress(null); - } - const textEmb = await clipText.forward(textPrompt); - const match = selectByText( - instances, - instanceEmbeddingsRef.current, - textEmb - ); - applyMatch(match); - } catch (e) { - console.error(e); - } finally { - setTextBusy(false); - } - } - - const handleCameraPress = async (isCamera: boolean) => { - Keyboard.dismiss(); - const image = await getImage(isCamera); - if (!image?.uri) return; - setImageUri(image.uri); - setImageSize({ width: image.width ?? 0, height: image.height ?? 0 }); - rawInstancesRef.current = []; - instanceEmbeddingsRef.current = null; - setSelection([]); - setInferenceTime(null); - }; - - const runForward = async () => { - Keyboard.dismiss(); - if (!imageUri) return; - try { - const start = Date.now(); - const output = await forward(imageUri, { - confidenceThreshold: 0.4, - iouThreshold: 0.9, - maxInstances: 50, - returnMaskAtOriginalResolution: true, - }); - setInferenceTime(Date.now() - start); - rawInstancesRef.current = output; - instanceEmbeddingsRef.current = null; - setSelection([]); - } catch (e) { - console.error(e); - } - }; - - if (!isReady && error) { - return ( - - - Error Loading Model - {error.message} - - - ); - } - - if (!isReady) { - return ( - - ); - } - - const { width: cw, height: ch } = layoutRef.current; - const { width: iw, height: ih } = imageSize; - const drawScale = iw > 0 && ih > 0 ? Math.min(cw / iw, ch / ih) : 1; - const offsetX = (cw - iw * drawScale) / 2; - const offsetY = (ch - ih * drawScale) / 2; - - const stepHint = !imageUri - ? null - : inferenceTime === null - ? 'Tap Run to detect instances' - : rawInstancesRef.current.length === 0 - ? 'No instances detected — try another image' - : selection.length === 0 - ? 'Tap a point, draw a box, or describe an object' - : null; - - return ( - - - - - - { - layoutRef.current = { - width: e.nativeEvent.layout.width, - height: e.nativeEvent.layout.height, - }; - }} - onTouchStart={(e) => { - Keyboard.dismiss(); - if (mode === 'point') handleTap(e); - else if (mode === 'box') handleBoxStart(e); - }} - onTouchMove={handleBoxMove} - onTouchEnd={handleBoxEnd} - > - - {draftBox && iw > 0 && ( - - - - )} - - {!imageUri && ( - - Segment Anything - - Segment any object in an image. (1) Pick an image, (2) tap - Run to detect instances, (3) tap a point, draw a box, or - describe an object to segment it. - - - )} - - - - {stepHint && {stepHint}} - - - {(['point', 'box', 'text'] as PromptMode[]).map((m) => { - const promptDisabled = rawInstancesRef.current.length === 0; - return ( - { - if (m !== 'text') Keyboard.dismiss(); - setMode(m); - }} - disabled={promptDisabled} - > - - {m[0]!.toUpperCase() + m.slice(1)} - - - ); - })} - - - {mode === 'text' && ( - - - {(() => { - const findInactive = - !textPrompt.trim() || - rawInstancesRef.current.length === 0 || - !clipImage.isReady || - !clipText.isReady; - return ( - - Find - - ); - })()} - - )} - {mode === 'text' && embeddingProgress && ( - - Embedding instances {embeddingProgress.done}/ - {embeddingProgress.total} (subsequent text queries are instant) - - )} - - { - if (m.modelName === selectedModel.modelName) return; - setSelectedModel(m); - rawInstancesRef.current = []; - instanceEmbeddingsRef.current = null; - setSelection([]); - setInferenceTime(null); - }} - /> - - 0 - ? rawInstancesRef.current.length - : null - } - /> - - - - - - ); -} - -async function cropAndEmbed( - image: SkImage, - bbox: Bbox, - mask: Uint8Array, - maskWidth: number, - maskHeight: number, - forward: (input: string) => Promise -): Promise { - const imgW = image.width(); - const imgH = image.height(); - const surface = Skia.Surface.MakeOffscreen(imgW, imgH); - if (!surface) throw new Error('Failed to create offscreen Skia surface'); - const canvas = surface.getCanvas(); - canvas.clear(Skia.Color('white')); - - const x1 = Math.max(0, Math.round(bbox.x1)); - const y1 = Math.max(0, Math.round(bbox.y1)); - const x2 = Math.min(imgW, Math.round(bbox.x2)); - const y2 = Math.min(imgH, Math.round(bbox.y2)); - const w = x2 - x1; - const h = y2 - y1; - if (w > 0 && h > 0) { - canvas.drawImageRect( - image, - { x: x1, y: y1, width: w, height: h }, - { x: x1, y: y1, width: w, height: h }, - Skia.Paint() - ); - } - - const inversePixels = new Uint8Array(mask.length * 4); - for (let i = 0; i < mask.length; i++) { - const outside = mask[i]! === 0; - const idx = i * 4; - inversePixels[idx] = outside ? 255 : 0; - inversePixels[idx + 1] = outside ? 255 : 0; - inversePixels[idx + 2] = outside ? 255 : 0; - inversePixels[idx + 3] = outside ? 255 : 0; - } - const inverseData = Skia.Data.fromBytes(inversePixels); - const inverseMaskImg = Skia.Image.MakeImage( - { - width: maskWidth, - height: maskHeight, - colorType: ColorType.RGBA_8888, - alphaType: AlphaType.Premul, - }, - inverseData, - maskWidth * 4 - ); - if (inverseMaskImg) { - canvas.drawImageRect( - inverseMaskImg, - { x: 0, y: 0, width: maskWidth, height: maskHeight }, - { - x: bbox.x1, - y: bbox.y1, - width: bbox.x2 - bbox.x1, - height: bbox.y2 - bbox.y1, - }, - Skia.Paint() - ); - } - - const base64 = surface.makeImageSnapshot().encodeToBase64(); - inverseData.dispose(); - return forward(`data:image/png;base64,${base64}`); -} - -const styles = StyleSheet.create({ - flex: { flex: 1 }, - container: { flex: 6, width: '100%' }, - imageContainer: { flex: 1, width: '100%', padding: 16 }, - imageTouchArea: { flex: 1, position: 'relative' }, - infoContainer: { alignItems: 'center', padding: 16, gap: 8 }, - infoTitle: { fontSize: 18, fontWeight: '600', color: 'navy' }, - infoText: { - fontSize: 14, - color: '#555', - textAlign: 'center', - lineHeight: 20, - }, - modeRow: { - flexDirection: 'row', - justifyContent: 'center', - paddingVertical: 8, - gap: 8, - }, - modeBtn: { - paddingHorizontal: 18, - paddingVertical: 8, - borderRadius: 8, - borderWidth: 1, - borderColor: ColorPalette.primary, - backgroundColor: '#fff', - }, - modeBtnActive: { backgroundColor: ColorPalette.primary }, - modeBtnDisabled: { borderColor: '#cbd5e1', backgroundColor: '#f8fafc' }, - modeBtnText: { fontSize: 14, fontWeight: '600', color: ColorPalette.primary }, - modeBtnTextActive: { color: '#fff' }, - modeBtnTextDisabled: { color: '#cbd5e1' }, - textRow: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 16, - paddingBottom: 8, - gap: 8, - }, - textInput: { - flex: 1, - backgroundColor: '#fff', - borderWidth: 1, - borderColor: ColorPalette.primary, - borderRadius: 12, - paddingHorizontal: 14, - paddingVertical: 12, - fontSize: 16, - color: '#0f172a', - }, - textBtn: { - backgroundColor: ColorPalette.primary, - borderRadius: 12, - paddingVertical: 14, - width: 80, - alignItems: 'center', - }, - textBtnDisabled: { backgroundColor: '#cbd5e1' }, - textBtnLabel: { color: '#fff', fontWeight: '700', fontSize: 16 }, - statusLine: { - paddingHorizontal: 16, - paddingBottom: 6, - fontSize: 12, - color: '#64748b', - }, - stepHint: { - paddingHorizontal: 16, - paddingTop: 6, - fontSize: 13, - fontWeight: '500', - color: ColorPalette.primary, - textAlign: 'center', - }, - errorContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - padding: 32, - }, - errorTitle: { - fontSize: 20, - fontWeight: '700', - color: '#e74c3c', - marginBottom: 12, - }, - errorText: { fontSize: 14, color: '#555', textAlign: 'center' }, -}); diff --git a/apps/computer-vision/app/semantic_segmentation/index.tsx b/apps/computer-vision/app/semantic_segmentation/index.tsx deleted file mode 100644 index f77c8de9c8..0000000000 --- a/apps/computer-vision/app/semantic_segmentation/index.tsx +++ /dev/null @@ -1,263 +0,0 @@ -import Spinner from '../../components/Spinner'; -import { BottomBar } from '../../components/BottomBar'; -import { ModelPicker, ModelOption } from '../../components/ModelPicker'; -import { getImage } from '../../utils'; -import { - models, - useSemanticSegmentation, - SemanticSegmentationModelSources, -} from 'react-native-executorch'; -import { - Canvas, - Image as SkiaImage, - Skia, - AlphaType, - ColorType, - SkImage, -} from '@shopify/react-native-skia'; -import { View, StyleSheet, Image, Text } from 'react-native'; -import React, { useContext, useEffect, useState } from 'react'; -import { GeneratingContext } from '../../context'; -import ScreenWrapper from '../../ScreenWrapper'; -import { StatsBar } from '../../components/StatsBar'; -import ErrorBanner from '../../components/ErrorBanner'; -const semanticSegmentation = models.semantic_segmentation; - -const numberToColor: number[][] = [ - [255, 87, 51], // 0 Red - [51, 255, 87], // 1 Green - [51, 87, 255], // 2 Blue - [255, 51, 246], // 3 Magenta - [51, 255, 246], // 4 Cyan - [243, 255, 51], // 5 Yellow - [141, 51, 255], // 6 Purple - [255, 131, 51], // 7 Orange - [51, 255, 131], // 8 Spring Green - [131, 51, 255], // 9 Violet - [255, 255, 51], // 10 Bright Yellow - [51, 255, 255], // 11 Aqua - [255, 51, 143], // 12 Deep Pink - [127, 51, 255], // 13 Dark Orchid - [51, 255, 175], // 14 Medium Spring Green - [255, 175, 51], // 15 Sandy Brown - [179, 255, 51], // 16 Chartreuse - [255, 87, 51], // 17 Red (darker shade) - [255, 51, 162], // 18 Hot Pink - [51, 162, 255], // 19 Sky Blue - [162, 51, 255], // 20 Amethyst -]; - -const MODELS: ModelOption[] = [ - { - label: 'DeepLab MobileNet', - value: semanticSegmentation.deeplab_v3_mobilenet_v3_large(), - }, - { - label: 'DeepLab ResNet50', - value: semanticSegmentation.deeplab_v3_resnet50(), - }, - { - label: 'DeepLab ResNet101', - value: semanticSegmentation.deeplab_v3_resnet101(), - }, - { - label: 'LRASPP MobileNet', - value: semanticSegmentation.lraspp_mobilenet_v3_large(), - }, - { - label: 'FCN ResNet50', - value: semanticSegmentation.fcn_resnet50(), - }, - { - label: 'FCN ResNet101', - value: semanticSegmentation.fcn_resnet101(), - }, - { - label: 'Selfie Segmentation', - value: semanticSegmentation.selfie_segmentation(), - }, -]; - -export default function SemanticSegmentationScreen() { - const { setGlobalGenerating } = useContext(GeneratingContext); - const [selectedModel, setSelectedModel] = - useState( - semanticSegmentation.deeplab_v3_mobilenet_v3_large() - ); - - const { - isReady, - isGenerating, - downloadProgress, - forward, - error: modelError, - } = useSemanticSegmentation({ model: selectedModel }); - - const [imageUri, setImageUri] = useState(''); - const [imageSize, setImageSize] = useState({ width: 0, height: 0 }); - const [segImage, setSegImage] = useState(null); - const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 }); - const [inferenceTime, setInferenceTime] = useState(null); - const [error, setError] = useState(null); - - useEffect(() => { - setGlobalGenerating(isGenerating); - }, [isGenerating, setGlobalGenerating]); - - useEffect(() => { - if (modelError) setError(String(modelError)); - }, [modelError]); - - const handleCameraPress = async (isCamera: boolean) => { - const image = await getImage(isCamera); - if (!image?.uri) return; - setImageUri(image.uri); - setImageSize({ width: image.width ?? 0, height: image.height ?? 0 }); - setSegImage(null); - setInferenceTime(null); - }; - - const runForward = async () => { - if (!imageUri || imageSize.width === 0 || imageSize.height === 0) return; - try { - const start = Date.now(); - const { width, height } = imageSize; - const output = await forward(imageUri, [], true); - const argmax = output.ARGMAX || []; - const pixels = new Uint8Array(width * height * 4); - - for (let row = 0; row < height; row++) { - for (let col = 0; col < width; col++) { - const idx = row * width + col; - const color = numberToColor[argmax[idx]] || [0, 0, 0]; - pixels[idx * 4] = color[0]; - pixels[idx * 4 + 1] = color[1]; - pixels[idx * 4 + 2] = color[2]; - pixels[idx * 4 + 3] = 255; - } - } - - const data = Skia.Data.fromBytes(pixels); - const img = Skia.Image.MakeImage( - { - width, - height, - alphaType: AlphaType.Opaque, - colorType: ColorType.RGBA_8888, - }, - data, - width * 4 - ); - setSegImage(img); - setInferenceTime(Date.now() - start); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - }; - - if (!isReady && !modelError) { - return ( - - ); - } - - return ( - - setError(null)} /> - - - - {!imageUri && ( - - Semantic Segmentation - - This model assigns a class label to every pixel in an image, - painting each region with a distinct color. Pick an image from - your gallery or take one with your camera to get started. - - - )} - - {segImage && ( - - setCanvasSize({ - width: e.nativeEvent.layout.width, - height: e.nativeEvent.layout.height, - }) - } - > - - - - - )} - - { - setSelectedModel(m); - setSegImage(null); - }} - /> - - - - ); -} - -const styles = StyleSheet.create({ - imageCanvasContainer: { flex: 6, width: '100%', padding: 16 }, - imageContainer: { flex: 1, width: '100%' }, - image: { flex: 1, borderRadius: 8, width: '100%' }, - canvasContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - gap: 4, - padding: 4, - }, - canvas: { width: '100%', height: '100%' }, - infoContainer: { - alignItems: 'center', - padding: 16, - gap: 8, - }, - infoTitle: { - fontSize: 18, - fontWeight: '600', - color: 'navy', - }, - infoText: { - fontSize: 14, - color: '#555', - textAlign: 'center', - lineHeight: 20, - }, -}); diff --git a/apps/computer-vision/app/style_transfer/index.tsx b/apps/computer-vision/app/style_transfer/index.tsx deleted file mode 100644 index d8eda655f7..0000000000 --- a/apps/computer-vision/app/style_transfer/index.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import Spinner from '../../components/Spinner'; -import { BottomBar } from '../../components/BottomBar'; -import { ModelPicker, ModelOption } from '../../components/ModelPicker'; -import { getImage } from '../../utils'; -import { - models, - useStyleTransfer, - StyleTransferModelName, - ResourceSource, -} from 'react-native-executorch'; - -import { View, StyleSheet, Image, Text } from 'react-native'; - -import React, { useContext, useEffect, useState } from 'react'; -import { GeneratingContext } from '../../context'; -import ScreenWrapper from '../../ScreenWrapper'; -import { StatsBar } from '../../components/StatsBar'; -const styleTransfer = models.style_transfer; - -type StyleTransferModelSources = { - modelName: StyleTransferModelName; - modelSource: ResourceSource; -}; - -const MODELS: ModelOption[] = [ - { label: 'Candy', value: styleTransfer.candy() }, - { label: 'Mosaic', value: styleTransfer.mosaic() }, - { - label: 'Rain Princess', - value: styleTransfer.rain_princess(), - }, - { label: 'Udnie', value: styleTransfer.udnie() }, -]; -import ErrorBanner from '../../components/ErrorBanner'; - -export default function StyleTransferScreen() { - const [selectedModel, setSelectedModel] = useState( - styleTransfer.candy() - ); - - const model = useStyleTransfer({ model: selectedModel }); - const { setGlobalGenerating } = useContext(GeneratingContext); - useEffect(() => { - setGlobalGenerating(model.isGenerating); - }, [model.isGenerating, setGlobalGenerating]); - - useEffect(() => { - if (model.error) setError(String(model.error)); - }, [model.error]); - - const [imageUri, setImageUri] = useState(''); - const [styledUri, setStyledUri] = useState(''); - const [inferenceTime, setInferenceTime] = useState(null); - const [error, setError] = useState(null); - - const handleCameraPress = async (isCamera: boolean) => { - const image = await getImage(isCamera); - const uri = image?.uri; - if (typeof uri === 'string') { - setImageUri(uri); - setStyledUri(''); - setInferenceTime(null); - } - }; - - const runForward = async () => { - if (imageUri) { - try { - const start = Date.now(); - const uri = await model.forward(imageUri, 'url'); - setInferenceTime(Date.now() - start); - setStyledUri(uri); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - } - }; - - if (!model.isReady && !model.error) { - return ( - - ); - } - - return ( - - setError(null)} /> - - - {!imageUri && ( - - Style Transfer - - This model applies artistic styles to your images, transforming - them to look like famous paintings. Pick an image from your - gallery or take one with your camera to get started. - - - )} - - { - setSelectedModel(m); - setStyledUri(''); - }} - /> - - - - ); -} - -const styles = StyleSheet.create({ - imageContainer: { flex: 6, width: '100%', padding: 16 }, - image: { flex: 1, borderRadius: 8, width: '100%' }, - infoContainer: { - alignItems: 'center', - padding: 16, - gap: 8, - }, - infoTitle: { - fontSize: 18, - fontWeight: '600', - color: 'navy', - }, - infoText: { - fontSize: 14, - color: '#555', - textAlign: 'center', - lineHeight: 20, - }, -}); diff --git a/apps/computer-vision/app/text_to_image/index.tsx b/apps/computer-vision/app/text_to_image/index.tsx deleted file mode 100644 index 0d61ea05e6..0000000000 --- a/apps/computer-vision/app/text_to_image/index.tsx +++ /dev/null @@ -1,332 +0,0 @@ -import { - View, - StyleSheet, - Text, - Image, - Keyboard, - TextInput, - TouchableOpacity, - TouchableWithoutFeedback, - KeyboardAvoidingView, - Platform, -} from 'react-native'; -import React, { useContext, useEffect, useState } from 'react'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import Spinner from '../../components/Spinner'; -import { - models, - useTextToImage, - TextToImageProps, -} from 'react-native-executorch'; -import { ModelPicker, ModelOption } from '../../components/ModelPicker'; -import { GeneratingContext } from '../../context'; -import ColorPalette from '../../colors'; -import ProgressBar from '../../components/ProgressBar'; -import { Ionicons } from '@expo/vector-icons'; -import { StatsBar } from '../../components/StatsBar'; -import ErrorBanner from '../../components/ErrorBanner'; -const imageGeneration = models.image_generation; - -type TextToImageModelSources = TextToImageProps['model']; - -const MODELS: ModelOption[] = [ - { - label: 'BK-SDM 256', - value: imageGeneration.bk_sdm_tiny_vpred_256(), - }, - { - label: 'BK-SDM 512', - value: imageGeneration.bk_sdm_tiny_vpred_512(), - }, -]; - -export default function TextToImageScreen() { - const { bottom } = useSafeAreaInsets(); - const [inferenceStepIdx, setInferenceStepIdx] = useState(0); - const [image, setImage] = useState(null); - const [steps, setSteps] = useState(40); - - const [input, setInput] = useState(''); - const [selectedModel, setSelectedModel] = useState( - imageGeneration.bk_sdm_tiny_vpred_256() - ); - const [generationTime, setGenerationTime] = useState(null); - - const [keyboardVisible, setKeyboardVisible] = useState(false); - const [error, setError] = useState(null); - const [imageTitle, setImageTitle] = useState(null); - - const imageSize = 224; - const model = useTextToImage({ - model: selectedModel, - inferenceCallback: (x) => setInferenceStepIdx(x), - }); - - const { setGlobalGenerating } = useContext(GeneratingContext); - - useEffect(() => { - setGlobalGenerating(model.isGenerating); - }, [model.isGenerating, setGlobalGenerating]); - - useEffect(() => { - if (model.error) setError(String(model.error)); - }, [model.error]); - - useEffect(() => { - const showSub = Keyboard.addListener('keyboardDidShow', () => { - setKeyboardVisible(true); - }); - const hideSub = Keyboard.addListener('keyboardDidHide', () => { - setKeyboardVisible(false); - }); - return () => { - showSub.remove(); - hideSub.remove(); - }; - }, []); - - const runForward = async () => { - if (!input.trim()) return; - - setImageTitle(input); - - try { - const start = Date.now(); - const output = await model.generate(input, imageSize, steps); - - if (output.length) { - setImage(output); - setGenerationTime(Date.now() - start); - } - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - setImageTitle(null); - } finally { - setInferenceStepIdx(0); - } - }; - - if (!model.isReady && !model.error) { - return ( - - ); - } - - return ( - { - Keyboard.dismiss(); - }} - > - - {keyboardVisible && } - - setError(null)} /> - - - {imageTitle && {imageTitle}} - - - - {model.isGenerating ? ( - - Generating... - - - ) : image?.length ? ( - - ) : ( - - Text to Image - - This model generates images from text descriptions using a - diffusion process. Type a prompt below and tap the send button - to generate an image. - - - )} - - - { - setSelectedModel(m); - setImage(null); - setGenerationTime(null); - }} - /> - - - Steps: {steps} - setSteps((s) => Math.max(5, s - 5))} - > - - - setSteps((s) => Math.min(50, s + 5))} - > - + - - - - - - - - {model.isGenerating ? ( - - - - ) : ( - - - - )} - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - width: '100%', - }, - overlay: { - ...StyleSheet.absoluteFill, - backgroundColor: 'rgba(0,0,0,0.1)', - zIndex: 1, - }, - titleContainer: { - paddingHorizontal: 16, - paddingTop: 8, - alignItems: 'center', - }, - titleText: { - fontSize: 18, - fontWeight: '600', - color: '#333', - textAlign: 'center', - }, - imageContainer: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - padding: 16, - zIndex: 0, - }, - image: { - width: 256, - height: 256, - }, - progressContainer: { - alignItems: 'center', - gap: 12, - }, - text: { - fontSize: 16, - color: ColorPalette.primary, - }, - stepsRow: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 16, - paddingVertical: 6, - gap: 8, - }, - stepsLabel: { - flex: 1, - fontSize: 14, - color: ColorPalette.primary, - }, - stepButton: { - width: 36, - height: 36, - borderRadius: 8, - backgroundColor: ColorPalette.primary, - alignItems: 'center', - justifyContent: 'center', - }, - stepButtonText: { - color: '#fff', - fontSize: 20, - lineHeight: 22, - }, - inputRow: { - flexDirection: 'row', - alignItems: 'center', - margin: 12, - gap: 8, - }, - textInput: { - flex: 1, - borderWidth: 1, - borderColor: '#C1C6E5', - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 10, - fontSize: 14, - color: ColorPalette.primary, - }, - sendButton: { - width: 44, - height: 44, - borderRadius: 8, - backgroundColor: ColorPalette.primary, - alignItems: 'center', - justifyContent: 'center', - }, - sendButtonDisabled: { - backgroundColor: '#888', - }, - infoContainer: { - alignItems: 'center', - padding: 16, - gap: 8, - }, - infoTitle: { - fontSize: 18, - fontWeight: '600', - color: 'navy', - }, - infoText: { - fontSize: 14, - color: '#555', - textAlign: 'center', - lineHeight: 20, - }, -}); diff --git a/apps/computer-vision/app/vision_camera/index.tsx b/apps/computer-vision/app/vision_camera/index.tsx deleted file mode 100644 index 65febb5ac8..0000000000 --- a/apps/computer-vision/app/vision_camera/index.tsx +++ /dev/null @@ -1,567 +0,0 @@ -import React, { useCallback, useContext, useEffect, useState } from 'react'; -import { - ScrollView, - StatusBar, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { useIsFocused } from 'expo-router'; -import { - Camera, - useCameraDevices, - useCameraPermission, - useFrameOutput, -} from 'react-native-vision-camera'; -import { createSynchronizable } from 'react-native-worklets'; -import Svg, { Path, Polygon } from 'react-native-svg'; -import { useRouter } from 'expo-router'; -import { Ionicons } from '@expo/vector-icons'; -import { GeneratingContext } from '../../context'; -import Spinner from '../../components/Spinner'; -import ColorPalette from '../../colors'; -import ClassificationTask from '../../components/vision_camera/tasks/ClassificationTask'; -import ObjectDetectionTask from '../../components/vision_camera/tasks/ObjectDetectionTask'; -import SegmentationTask from '../../components/vision_camera/tasks/SegmentationTask'; -import InstanceSegmentationTask from '../../components/vision_camera/tasks/InstanceSegmentationTask'; -import OCRTask from '../../components/vision_camera/tasks/OCRTask'; -import StyleTransferTask from '../../components/vision_camera/tasks/StyleTransferTask'; -import PoseEstimationTask from '../../components/vision_camera/tasks/PoseEstimationTask'; -// 1. Import ErrorBanner -import ErrorBanner from '../../components/ErrorBanner'; - -type TaskId = - | 'classification' - | 'objectDetection' - | 'segmentation' - | 'instanceSegmentation' - | 'poseEstimation' - | 'ocr' - | 'styleTransfer'; -type ModelId = - | 'classification' - | 'objectDetectionSsdlite' - | 'objectDetectionRfdetr' - | 'objectDetectionYolo26n' - | 'segmentationDeeplabResnet50' - | 'segmentationDeeplabResnet101' - | 'segmentationDeeplabMobilenet' - | 'segmentationLraspp' - | 'segmentationFcnResnet50' - | 'segmentationFcnResnet101' - | 'segmentationSelfie' - | 'instanceSegmentationYolo26n' - | 'instanceSegmentationRfdetr' - | 'instanceSegmentationFastsamS' - | 'instanceSegmentationFastsamX' - | 'poseEstimationYolo26n' - | 'ocr' - | 'styleTransferCandy' - | 'styleTransferMosaic'; - -type TaskVariant = { id: ModelId; label: string }; -type Task = { id: TaskId; label: string; variants: TaskVariant[] }; - -const TASKS: Task[] = [ - { - id: 'classification', - label: 'Classify', - variants: [{ id: 'classification', label: 'EfficientNet V2 S' }], - }, - { - id: 'segmentation', - label: 'Segment', - variants: [ - { id: 'segmentationDeeplabResnet50', label: 'DeepLab ResNet50' }, - { id: 'segmentationDeeplabResnet101', label: 'DeepLab ResNet101' }, - { id: 'segmentationDeeplabMobilenet', label: 'DeepLab MobileNet' }, - { id: 'segmentationLraspp', label: 'LRASPP MobileNet' }, - { id: 'segmentationFcnResnet50', label: 'FCN ResNet50' }, - { id: 'segmentationFcnResnet101', label: 'FCN ResNet101' }, - { id: 'segmentationSelfie', label: 'Selfie' }, - ], - }, - { - id: 'instanceSegmentation', - label: 'Inst Seg', - variants: [ - { id: 'instanceSegmentationYolo26n', label: 'YOLO26N Seg' }, - { id: 'instanceSegmentationRfdetr', label: 'RF-DETR Nano Seg' }, - { id: 'instanceSegmentationFastsamS', label: 'FastSAM-S' }, - { id: 'instanceSegmentationFastsamX', label: 'FastSAM-X' }, - ], - }, - { - id: 'poseEstimation', - label: 'Pose', - variants: [{ id: 'poseEstimationYolo26n', label: 'YOLO26N Pose' }], - }, - { - id: 'objectDetection', - label: 'Detect', - variants: [ - { id: 'objectDetectionSsdlite', label: 'SSDLite MobileNet' }, - { id: 'objectDetectionRfdetr', label: 'RF-DETR Nano' }, - { id: 'objectDetectionYolo26n', label: 'YOLO26N' }, - ], - }, - { - id: 'ocr', - label: 'OCR', - variants: [{ id: 'ocr', label: 'English' }], - }, - { - id: 'styleTransfer', - label: 'Style', - variants: [ - { id: 'styleTransferCandy', label: 'Candy' }, - { id: 'styleTransferMosaic', label: 'Mosaic' }, - ], - }, -]; - -export default function VisionCameraScreen() { - const insets = useSafeAreaInsets(); - const router = useRouter(); - const [frameKillSwitch] = useState(() => createSynchronizable(false)); - const [cameraPositionSync] = useState(() => - createSynchronizable<'front' | 'back'>('back') - ); - const [activeTask, setActiveTask] = useState('classification'); - const [activeModel, setActiveModel] = useState('classification'); - const [canvasSize, setCanvasSize] = useState({ width: 1, height: 1 }); - const [cameraPosition, setCameraPosition] = useState<'front' | 'back'>( - 'back' - ); - const [fps, setFps] = useState(0); - const [frameMs, setFrameMs] = useState(0); - const [isReady, setIsReady] = useState(false); - const [downloadProgress, setDownloadProgress] = useState(0); - const [frameOutput, setFrameOutput] = useState | null>(null); - - const [error, setError] = useState(null); - - const { setGlobalGenerating } = useContext(GeneratingContext); - - const isFocused = useIsFocused(); - const cameraPermission = useCameraPermission(); - const devices = useCameraDevices(); - const device = - devices.find((d) => d.position === cameraPosition) ?? devices[0]; - useEffect(() => { - frameKillSwitch.setBlocking(true); - setError(null); - const id = setTimeout(() => { - frameKillSwitch.setBlocking(false); - }, 300); - return () => clearTimeout(id); - }, [activeModel, frameKillSwitch]); - - useEffect(() => { - cameraPositionSync.setBlocking(cameraPosition); - }, [cameraPosition, cameraPositionSync]); - - const handleFpsChange = useCallback((newFps: number, newMs: number) => { - setFps(newFps); - setFrameMs(newMs); - }, []); - - const handleGeneratingChange = useCallback( - (generating: boolean) => { - setGlobalGenerating(generating); - }, - [setGlobalGenerating] - ); - - const handleErrorChange = useCallback((errorMessage: string | null) => { - setError(errorMessage); - }, []); - - if (!cameraPermission.hasPermission) { - return ( - - Camera access needed - cameraPermission.requestPermission()} - style={styles.button} - > - Grant Permission - - - ); - } - - if (device == null) { - return ( - - No camera device found - - ); - } - - const activeTaskInfo = TASKS.find((t) => t.id === activeTask)!; - const activeVariantLabel = - activeTaskInfo.variants.find((v) => v.id === activeModel)?.label ?? - activeTaskInfo.variants[0]!.label; - - const taskProps = { - activeModel, - canvasSize, - cameraPositionSync, - frameKillSwitch, - onFrameOutputChange: setFrameOutput, - onReadyChange: setIsReady, - onProgressChange: setDownloadProgress, - onGeneratingChange: handleGeneratingChange, - onFpsChange: handleFpsChange, - onErrorChange: handleErrorChange, - }; - - return ( - - - - - setError(null)} /> - - - { - console.warn('[Camera] onError', e); - setError(e.message); - }} - onStarted={() => console.log('[Camera] session started')} - onPreviewStarted={() => console.log('[Camera] preview got first frame')} - /> - - - setCanvasSize({ - width: e.nativeEvent.layout.width, - height: e.nativeEvent.layout.height, - }) - } - /> - - {activeTask === 'classification' && } - {activeTask === 'objectDetection' && ( - - )} - {activeTask === 'segmentation' && ( - - )} - {activeTask === 'instanceSegmentation' && ( - - )} - {activeTask === 'poseEstimation' && ( - - )} - {activeTask === 'ocr' && } - {activeTask === 'styleTransfer' && ( - - )} - - {!isReady && !error && ( - - - - )} - - - router.navigate('/')} - > - - - - - {activeVariantLabel} - - {fps} FPS – {frameMs.toFixed(0)} ms - - - - - {TASKS.map((t) => ( - { - setActiveTask(t.id); - setActiveModel(t.variants[0]!.id); - }} - > - - {t.label} - - - ))} - - - - {activeTaskInfo.variants.map((v) => ( - setActiveModel(v.id)} - > - - {v.label} - - - ))} - - - - - - setCameraPosition((p) => (p === 'back' ? 'front' : 'back')) - } - > - - - - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: 'black' }, - errorOverlay: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - zIndex: 100, - }, - centered: { - flex: 1, - backgroundColor: 'black', - justifyContent: 'center', - alignItems: 'center', - gap: 16, - }, - message: { color: 'white', fontSize: 18 }, - button: { - paddingHorizontal: 24, - paddingVertical: 12, - backgroundColor: ColorPalette.primary, - borderRadius: 24, - }, - buttonText: { color: 'white', fontSize: 15, fontWeight: '600' }, - loadingOverlay: { - ...StyleSheet.absoluteFill, - backgroundColor: 'rgba(0,0,0,0.6)', - justifyContent: 'center', - alignItems: 'center', - zIndex: 10, - }, - topOverlay: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - alignItems: 'center', - gap: 8, - zIndex: 5, - }, - titleRow: { - alignItems: 'center', - paddingHorizontal: 16, - }, - modelTitle: { - color: 'white', - fontSize: 22, - fontWeight: '700', - textShadowColor: 'rgba(0,0,0,0.7)', - textShadowOffset: { width: 0, height: 1 }, - textShadowRadius: 4, - }, - fpsText: { - color: 'rgba(255,255,255,0.85)', - fontSize: 14, - fontWeight: '500', - marginTop: 2, - textShadowColor: 'rgba(0,0,0,0.7)', - textShadowOffset: { width: 0, height: 1 }, - textShadowRadius: 4, - }, - tabsContent: { - paddingHorizontal: 12, - gap: 6, - }, - tab: { - paddingHorizontal: 18, - paddingVertical: 7, - borderRadius: 20, - backgroundColor: 'rgba(0,0,0,0.45)', - borderWidth: 1, - borderColor: 'rgba(255,255,255,0.25)', - }, - tabActive: { - backgroundColor: 'rgba(255,255,255,0.2)', - borderColor: 'white', - }, - tabText: { - color: 'rgba(255,255,255,0.7)', - fontSize: 14, - fontWeight: '600', - }, - tabTextActive: { color: 'white' }, - chipsContent: { - paddingHorizontal: 12, - gap: 6, - }, - variantChip: { - paddingHorizontal: 14, - paddingVertical: 5, - borderRadius: 16, - backgroundColor: 'rgba(0,0,0,0.35)', - borderWidth: 1, - borderColor: 'rgba(255,255,255,0.15)', - }, - variantChipActive: { - backgroundColor: ColorPalette.primary, - borderColor: ColorPalette.primary, - }, - variantChipText: { - color: 'rgba(255,255,255,0.6)', - fontSize: 12, - fontWeight: '500', - }, - variantChipTextActive: { color: 'white' }, - bottomOverlay: { - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - alignItems: 'center', - zIndex: 5, - }, - flipButton: { - width: 56, - height: 56, - borderRadius: 28, - backgroundColor: 'rgba(255,255,255,0.2)', - justifyContent: 'center', - alignItems: 'center', - borderWidth: 1.5, - borderColor: 'rgba(255,255,255,0.4)', - }, - backButton: { - position: 'absolute', - left: 12, - width: 40, - height: 40, - borderRadius: 20, - backgroundColor: 'rgba(0,0,0,0.45)', - justifyContent: 'center', - alignItems: 'center', - borderWidth: 1, - borderColor: 'rgba(255,255,255,0.25)', - zIndex: 10, - }, -}); diff --git a/apps/computer-vision/assets/fonts/Aeonik-Medium.otf b/apps/computer-vision/assets/fonts/Aeonik-Medium.otf deleted file mode 100644 index cd9981f439..0000000000 Binary files a/apps/computer-vision/assets/fonts/Aeonik-Medium.otf and /dev/null differ diff --git a/apps/computer-vision/assets/fonts/Aeonik-Regular.otf b/apps/computer-vision/assets/fonts/Aeonik-Regular.otf deleted file mode 100644 index 9bd378ed2b..0000000000 Binary files a/apps/computer-vision/assets/fonts/Aeonik-Regular.otf and /dev/null differ diff --git a/apps/computer-vision/assets/icons/adaptive-icon.png b/apps/computer-vision/assets/icons/adaptive-icon.png deleted file mode 100644 index 03d6f6b6c6..0000000000 Binary files a/apps/computer-vision/assets/icons/adaptive-icon.png and /dev/null differ diff --git a/apps/computer-vision/assets/icons/executorch.svg b/apps/computer-vision/assets/icons/executorch.svg deleted file mode 100644 index e548ea4201..0000000000 --- a/apps/computer-vision/assets/icons/executorch.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/apps/computer-vision/assets/icons/executorch_logo.png b/apps/computer-vision/assets/icons/executorch_logo.png deleted file mode 100644 index 73bdec1b7c..0000000000 Binary files a/apps/computer-vision/assets/icons/executorch_logo.png and /dev/null differ diff --git a/apps/computer-vision/assets/icons/favicon.png b/apps/computer-vision/assets/icons/favicon.png deleted file mode 100644 index e75f697b18..0000000000 Binary files a/apps/computer-vision/assets/icons/favicon.png and /dev/null differ diff --git a/apps/computer-vision/assets/icons/icon.png b/apps/computer-vision/assets/icons/icon.png deleted file mode 100644 index a0b1526fc7..0000000000 Binary files a/apps/computer-vision/assets/icons/icon.png and /dev/null differ diff --git a/apps/computer-vision/assets/icons/splash.png b/apps/computer-vision/assets/icons/splash.png deleted file mode 100644 index 0e89705a94..0000000000 Binary files a/apps/computer-vision/assets/icons/splash.png and /dev/null differ diff --git a/apps/computer-vision/assets/icons/swm_icon.svg b/apps/computer-vision/assets/icons/swm_icon.svg deleted file mode 100644 index 8c62f039be..0000000000 --- a/apps/computer-vision/assets/icons/swm_icon.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/apps/computer-vision/babel.config.js b/apps/computer-vision/babel.config.js deleted file mode 100644 index 6b2006979c..0000000000 --- a/apps/computer-vision/babel.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = function (api) { - api.cache(true); - return { - presets: ['babel-preset-expo'], - plugins: ['react-native-worklets/plugin'], - }; -}; diff --git a/apps/computer-vision/colors.ts b/apps/computer-vision/colors.ts deleted file mode 100644 index feb75ac336..0000000000 --- a/apps/computer-vision/colors.ts +++ /dev/null @@ -1,6 +0,0 @@ -const ColorPalette = { - primary: '#001A72', - strongPrimary: '#020F3C', -}; - -export default ColorPalette; diff --git a/apps/computer-vision/components/BottomBar.tsx b/apps/computer-vision/components/BottomBar.tsx deleted file mode 100644 index 617abe11fc..0000000000 --- a/apps/computer-vision/components/BottomBar.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import ColorPalette from '../colors'; -import FontAwesome from '@expo/vector-icons/FontAwesome'; -import { View, TouchableOpacity, StyleSheet, Text } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import DeviceInfo from 'react-native-device-info'; - -const isDevice = !DeviceInfo.isEmulatorSync(); - -export const BottomBar = ({ - handleCameraPress, - runForward, - hasImage = true, - isGenerating = false, -}: { - handleCameraPress: (isCamera: boolean) => void; - runForward: () => void; - hasImage?: boolean; - isGenerating?: boolean; -}) => { - const { bottom } = useSafeAreaInsets(); - const disabled = !hasImage || isGenerating; - return ( - - - handleCameraPress(false)}> - - - isDevice && handleCameraPress(true)}> - - - - - - {isGenerating - ? 'Running...' - : hasImage - ? 'Run model' - : 'Pick an image to run the model'} - - - - ); -}; - -const styles = StyleSheet.create({ - bottomContainer: { - width: '100%', - gap: 15, - alignItems: 'center', - paddingTop: 16, - paddingHorizontal: 16, - }, - bottomIconsContainer: { - flexDirection: 'row', - justifyContent: 'space-evenly', - width: '100%', - }, - button: { - width: '100%', - height: 50, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: ColorPalette.primary, - color: '#fff', - borderRadius: 8, - }, - buttonDisabled: { - backgroundColor: '#888', - }, - buttonText: { - color: '#fff', - fontSize: 16, - }, -}); diff --git a/apps/computer-vision/components/BottomBarWithTextInput.tsx b/apps/computer-vision/components/BottomBarWithTextInput.tsx deleted file mode 100644 index 8cffec3958..0000000000 --- a/apps/computer-vision/components/BottomBarWithTextInput.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import React, { useState } from 'react'; -import { - View, - Text, - TextInput, - TouchableOpacity, - StyleSheet, - KeyboardAvoidingView, - Platform, -} from 'react-native'; -import { Ionicons } from '@expo/vector-icons'; -import ColorPalette from '../colors'; - -interface BottomBarProps { - runModel: (input: string, numSteps: number) => void; - stopModel: () => void; - numSteps: number; - setSteps: React.Dispatch>; - isGenerating?: boolean; - isReady?: boolean; - showTextInput: boolean; - setShowTextInput: React.Dispatch>; - keyboardVisible: boolean; -} - -export const BottomBarWithTextInput = ({ - runModel, - stopModel, - numSteps, - setSteps, - isGenerating, - isReady, - showTextInput, - setShowTextInput, - keyboardVisible, -}: BottomBarProps) => { - const [input, setInput] = useState(''); - - const decreaseSteps = () => setSteps((prev) => Math.max(5, prev - 5)); - const increaseSteps = () => setSteps((prev) => Math.min(50, prev + 5)); - - if (!showTextInput) { - if (isGenerating) { - return ( - - Stop model - - ); - } else { - return ( - setShowTextInput(true)} - disabled={!isReady} - > - Run model - - ); - } - } - - return ( - - - - { - setShowTextInput(false); - setInput(''); - runModel(input, numSteps); - }} - disabled={!isReady || isGenerating} - > - - - - - - - Steps: {numSteps} - - - - - - - - + - - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - alignItems: 'center', - }, - inputContainer: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - }, - input: { - flex: 1, - borderRadius: 6, - padding: 8, - marginRight: 8, - backgroundColor: '#fff', - color: '#000', - }, - stepsContainer: { - width: '100%', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - marginTop: 10, - }, - stepsButtons: { - flexDirection: 'row', - }, - button: { - width: '100%', - height: 40, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: ColorPalette.primary, - borderRadius: 8, - }, - buttonText: { - color: '#fff', - fontSize: 16, - textAlign: 'center', - }, - iconButton: { - marginHorizontal: 5, - width: 40, - }, - text: { - flex: 1, - fontSize: 16, - color: '#000', - }, - textWhite: { - color: '#fff', - }, -}); diff --git a/apps/computer-vision/components/BoundingBoxes.tsx b/apps/computer-vision/components/BoundingBoxes.tsx deleted file mode 100644 index 0c1e41c261..0000000000 --- a/apps/computer-vision/components/BoundingBoxes.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import { StyleSheet, Text, View } from 'react-native'; -import { Detection, LabelEnum } from 'react-native-executorch'; -import { labelColor, labelColorBg } from './utils/colors'; - -interface Props { - detections: Detection[]; - scaleX: number; - scaleY: number; - offsetX: number; - offsetY: number; - mirrorLabels?: boolean; - containerWidth?: number; -} - -export default function BoundingBoxes({ - detections, - scaleX, - scaleY, - offsetX, - offsetY, - mirrorLabels = false, - containerWidth, -}: Props) { - return ( - <> - {detections.map((det, i) => { - const left = det.bbox.x1 * scaleX + offsetX; - const top = det.bbox.y1 * scaleY + offsetY; - const width = (det.bbox.x2 - det.bbox.x1) * scaleX; - const height = (det.bbox.y2 - det.bbox.y1) * scaleY; - const labelTop = top < 26 ? top + height + 2 : top - 26; - - return ( - - - - - {String(det.label)} ({(det.score * 100).toFixed(1)}%) - - - - ); - })} - - ); -} - -const styles = StyleSheet.create({ - bbox: { - position: 'absolute', - borderWidth: 2, - borderRadius: 4, - }, - label: { - position: 'absolute', - paddingHorizontal: 6, - paddingVertical: 2, - borderRadius: 4, - }, - labelText: { - color: 'white', - fontSize: 11, - fontWeight: '600', - }, -}); diff --git a/apps/computer-vision/components/ErrorBanner.tsx b/apps/computer-vision/components/ErrorBanner.tsx deleted file mode 100644 index a5bebc504f..0000000000 --- a/apps/computer-vision/components/ErrorBanner.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; - -interface ErrorBannerProps { - message: string | null; - onDismiss: () => void; -} - -export default function ErrorBanner({ message, onDismiss }: ErrorBannerProps) { - if (!message) return null; - - return ( - - - {message} - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - backgroundColor: '#FEE2E2', - borderLeftWidth: 4, - borderLeftColor: '#EF4444', - borderRadius: 8, - marginHorizontal: 16, - marginVertical: 8, - paddingVertical: 10, - paddingLeft: 12, - paddingRight: 8, - flexDirection: 'row', - alignItems: 'center', - }, - message: { - flex: 1, - color: '#991B1B', - fontSize: 14, - lineHeight: 20, - }, - closeButton: { - padding: 4, - marginLeft: 8, - }, - closeText: { - color: '#991B1B', - fontSize: 16, - fontWeight: '600', - }, -}); diff --git a/apps/computer-vision/components/ImageWithBboxes.tsx b/apps/computer-vision/components/ImageWithBboxes.tsx deleted file mode 100644 index 9b13f314c6..0000000000 --- a/apps/computer-vision/components/ImageWithBboxes.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import React from 'react'; -import { Image, StyleSheet, View } from 'react-native'; -import { Detection } from 'react-native-executorch'; -import BoundingBoxes from './BoundingBoxes'; - -interface Props { - imageUri: string; - detections: Detection[]; - imageWidth: number; - imageHeight: number; -} - -export default function ImageWithBboxes({ - imageUri, - detections, - imageWidth, - imageHeight, -}: Props) { - const [layout, setLayout] = React.useState({ width: 0, height: 0 }); - - const calculateAdjustedDimensions = () => { - const imageRatio = imageWidth / imageHeight; - const layoutRatio = layout.width / layout.height; - - let sx, sy; - if (imageRatio > layoutRatio) { - // image is more "wide" - sx = layout.width / imageWidth; - sy = layout.width / imageRatio / imageHeight; - } else { - // image is more "tall" - sy = layout.height / imageHeight; - sx = (layout.height * imageRatio) / imageWidth; - } - - return { - scaleX: sx, - scaleY: sy, - offsetX: (layout.width - imageWidth * sx) / 2, - offsetY: (layout.height - imageHeight * sy) / 2, - }; - }; - - const { scaleX, scaleY, offsetX, offsetY } = calculateAdjustedDimensions(); - - return ( - { - const { width, height } = event.nativeEvent.layout; - setLayout({ width, height }); - }} - > - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - position: 'relative', - }, - image: { - flex: 1, - width: '100%', - height: '100%', - }, -}); diff --git a/apps/computer-vision/components/ImageWithMasks.tsx b/apps/computer-vision/components/ImageWithMasks.tsx deleted file mode 100644 index 53f7152100..0000000000 --- a/apps/computer-vision/components/ImageWithMasks.tsx +++ /dev/null @@ -1,258 +0,0 @@ -import React, { useState } from 'react'; -import { Image, StyleSheet, View, Text } from 'react-native'; -import { - Canvas, - Image as SkiaImage, - Skia, - AlphaType, - ColorType, - SkImage, - Rect, - Group, -} from '@shopify/react-native-skia'; - -const INSTANCE_COLORS = [ - [255, 87, 51, 180], - [51, 255, 87, 180], - [51, 87, 255, 180], - [255, 51, 246, 180], - [51, 255, 246, 180], - [243, 255, 51, 180], - [141, 51, 255, 180], - [255, 131, 51, 180], - [51, 255, 131, 180], - [131, 51, 255, 180], -]; - -const MAX_MASK_DIM = 256; - -/** Display-only data — no raw mask buffers. */ -export interface DisplayInstance { - bbox: { x1: number; y1: number; x2: number; y2: number }; - label: string; - score: number; - maskImage: SkImage; -} - -/** - * Convert raw segmentation output into lightweight display instances. - * Call this eagerly (in the forward callback) so raw Uint8Array masks - * can be garbage-collected immediately. - * @param rawInstances - Array of raw segmentation instances with mask buffers to convert. - * @returns Array of lightweight {@link DisplayInstance} objects with pre-rendered Skia images. - */ -export function buildDisplayInstances( - rawInstances: { - bbox: { x1: number; y1: number; x2: number; y2: number }; - mask: Uint8Array; - maskWidth: number; - maskHeight: number; - label: string | number; - score: number; - }[] -): DisplayInstance[] { - return rawInstances - .map((inst, i) => { - const color = INSTANCE_COLORS[i % INSTANCE_COLORS.length]; - const img = createMaskImage( - inst.mask, - inst.maskWidth, - inst.maskHeight, - color - ); - if (!img) return null; - return { - bbox: inst.bbox, - label: String(inst.label), - score: inst.score, - maskImage: img, - }; - }) - .filter((d): d is DisplayInstance => d !== null); -} - -function createMaskImage( - mask: Uint8Array, - srcW: number, - srcH: number, - color: number[] -): SkImage | null { - const downscale = Math.min(1, MAX_MASK_DIM / Math.max(srcW, srcH)); - const dstW = Math.max(1, Math.round(srcW * downscale)); - const dstH = Math.max(1, Math.round(srcH * downscale)); - - const pixels = new Uint8Array(dstW * dstH * 4); - const r = color[0], - g = color[1], - b = color[2], - a = color[3]; - - for (let dy = 0; dy < dstH; dy++) { - const sy = Math.min(Math.floor(dy / downscale), srcH - 1); - for (let dx = 0; dx < dstW; dx++) { - const sx = Math.min(Math.floor(dx / downscale), srcW - 1); - if (mask[sy * srcW + sx] > 0) { - const idx = (dy * dstW + dx) * 4; - pixels[idx] = r; - pixels[idx + 1] = g; - pixels[idx + 2] = b; - pixels[idx + 3] = a; - } - } - } - - const data = Skia.Data.fromBytes(pixels); - const image = Skia.Image.MakeImage( - { - width: dstW, - height: dstH, - alphaType: AlphaType.Premul, - colorType: ColorType.RGBA_8888, - }, - data, - dstW * 4 - ); - data.dispose(); - return image; -} - -interface Props { - imageUri: string; - instances: DisplayInstance[]; - imageWidth: number; - imageHeight: number; -} - -export default function ImageWithMasks({ - imageUri, - instances, - imageWidth, - imageHeight, -}: Props) { - const [layout, setLayout] = useState({ width: 0, height: 0 }); - - const scaleX = layout.width / (imageWidth || 1); - const scaleY = layout.height / (imageHeight || 1); - const scale = Math.min(scaleX, scaleY); - const offsetX = (layout.width - imageWidth * scale) / 2; - const offsetY = (layout.height - imageHeight * scale) / 2; - - return ( - { - const { width, height } = e.nativeEvent.layout; - setLayout({ width, height }); - }} - > - - - {instances.length > 0 && ( - - - {instances.map((inst, idx) => { - const mx = inst.bbox.x1 * scale + offsetX; - const my = inst.bbox.y1 * scale + offsetY; - const mw = (inst.bbox.x2 - inst.bbox.x1) * scale; - const mh = (inst.bbox.y2 - inst.bbox.y1) * scale; - return ( - - ); - })} - - {instances.map((inst, idx) => { - const color = INSTANCE_COLORS[idx % INSTANCE_COLORS.length]; - const bboxX = inst.bbox.x1 * scale + offsetX; - const bboxY = inst.bbox.y1 * scale + offsetY; - const bboxW = (inst.bbox.x2 - inst.bbox.x1) * scale; - const bboxH = (inst.bbox.y2 - inst.bbox.y1) * scale; - - return ( - - - - ); - })} - - - {instances.map((inst, idx) => { - const color = INSTANCE_COLORS[idx % INSTANCE_COLORS.length]; - const bboxX = inst.bbox.x1 * scale + offsetX; - const bboxY = inst.bbox.y1 * scale + offsetY; - - return ( - - - {inst.label || 'Unknown'} {(inst.score * 100).toFixed(0)}% - - - ); - })} - - )} - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - position: 'relative', - }, - image: { - width: '100%', - height: '100%', - }, - overlay: { - ...StyleSheet.absoluteFill, - }, - canvas: { - width: '100%', - height: '100%', - }, - labelContainer: { - position: 'absolute', - paddingHorizontal: 6, - paddingVertical: 2, - borderRadius: 4, - }, - labelText: { - color: 'white', - fontSize: 12, - fontWeight: '600', - }, -}); diff --git a/apps/computer-vision/components/ImageWithOCRBboxes.tsx b/apps/computer-vision/components/ImageWithOCRBboxes.tsx deleted file mode 100644 index eb1f9acbf6..0000000000 --- a/apps/computer-vision/components/ImageWithOCRBboxes.tsx +++ /dev/null @@ -1,104 +0,0 @@ -// Import necessary components -import React from 'react'; -import { Image, StyleSheet, View } from 'react-native'; -import Svg, { Polygon } from 'react-native-svg'; -import { OCRDetection } from 'react-native-executorch'; - -interface Props { - imageUri: string; - detections: OCRDetection[]; - imageWidth: number; - imageHeight: number; -} - -export default function ImageWithOCRBboxes({ - imageUri, - detections, - imageWidth, - imageHeight, -}: Props) { - const [layout, setLayout] = React.useState({ width: 0, height: 0 }); - - const calculateAdjustedDimensions = () => { - const imageRatio = imageWidth / imageHeight; - const layoutRatio = layout.width / layout.height; - let sx, sy; - if (imageRatio > layoutRatio) { - sx = layout.width / imageWidth; - sy = layout.width / imageRatio / imageHeight; - } else { - sy = layout.height / imageHeight; - sx = (layout.height * imageRatio) / imageWidth; - } - return { - scaleX: sx, - scaleY: sy, - offsetX: (layout.width - imageWidth * sx) / 2, - offsetY: (layout.height - imageHeight * sy) / 2, - }; - }; - - return ( - { - const { width, height } = event.nativeEvent.layout; - setLayout({ width, height }); - }} - > - - - {detections.map((detection, index) => { - const { scaleX, scaleY, offsetX, offsetY } = - calculateAdjustedDimensions(); - const { x1, y1, x2, y2 } = detection.bbox; - const pointsString = [ - [x1, y1], - [x2, y1], - [x2, y2], - [x1, y2], - ] - .map(([x, y]) => `${x * scaleX + offsetX},${y * scaleY + offsetY}`) - .join(' '); - - return ( - - ); - })} - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - position: 'relative', - }, - image: { - flex: 1, - width: '100%', - height: '100%', - }, - svgContainer: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - }, -}); diff --git a/apps/computer-vision/components/ModelPicker.tsx b/apps/computer-vision/components/ModelPicker.tsx deleted file mode 100644 index 94a848596e..0000000000 --- a/apps/computer-vision/components/ModelPicker.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { - Dimensions, - Modal, - ScrollView, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; - -export type ModelOption = { - label: string; - value: T; -}; - -type Props = { - models: ModelOption[]; - selectedModel: T; - onSelect: (model: T) => void; - label?: string; - disabled?: boolean; -}; - -const DROPDOWN_MAX_HEIGHT = 200; - -export function ModelPicker({ - models, - selectedModel, - onSelect, - label, - disabled, -}: Props) { - const [open, setOpen] = useState(false); - const [triggerHeight, setTriggerHeight] = useState(0); - const [expandUp, setExpandUp] = useState(false); - const [dropdownTop, setDropdownTop] = useState(0); - const triggerRef = useRef>(null); - const selected = models.find((m) => m.value === selectedModel); - - useEffect(() => { - if (disabled) setOpen(false); - }, [disabled]); - - const handlePress = () => { - if (disabled) return; - if (open) { - setOpen(false); - return; - } - triggerRef.current?.measure( - ( - _x: number, - _y: number, - _width: number, - height: number, - _pageX: number, - pageY: number - ) => { - setTriggerHeight(height); - const spaceBelow = Dimensions.get('window').height - (pageY + height); - setExpandUp(spaceBelow < DROPDOWN_MAX_HEIGHT); - setDropdownTop(pageY); - setOpen(true); - } - ); - }; - - const dropdownStylePosition = expandUp - ? { - bottom: Dimensions.get('window').height - dropdownTop, - left: 12, - right: 12, - } - : { - top: dropdownTop + triggerHeight + 2, - left: 12, - right: 12, - }; - - return ( - <> - - - {label && {label}} - {selected?.label ?? '—'} - {open ? '▲' : '▼'} - - - - {open && ( - setOpen(false)} - animationType="none" - > - setOpen(false)} - /> - - - {models.map((item) => { - const isSelected = item.value === selectedModel; - return ( - { - onSelect(item.value); - setOpen(false); - }} - activeOpacity={0.7} - > - - {item.label} - - - ); - })} - - - - )} - - ); -} - -const styles = StyleSheet.create({ - container: { - marginHorizontal: 12, - marginVertical: 4, - alignSelf: 'stretch', - zIndex: 100, - }, - trigger: { - flexDirection: 'row', - alignItems: 'center', - borderWidth: 1, - borderColor: '#C1C6E5', - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 10, - backgroundColor: '#f5f5f5', - }, - triggerDisabled: { - opacity: 0.4, - }, - label: { - fontSize: 12, - color: '#888', - marginRight: 6, - }, - triggerText: { - flex: 1, - fontSize: 14, - color: '#001A72', - fontWeight: '500', - }, - chevron: { - fontSize: 10, - color: '#888', - marginLeft: 6, - }, - modalBackdrop: { - flex: 1, - backgroundColor: 'rgba(0, 0, 0, 0.3)', - }, - dropdown: { - position: 'absolute', - borderWidth: 1, - borderColor: '#C1C6E5', - borderRadius: 8, - backgroundColor: '#fff', - maxHeight: DROPDOWN_MAX_HEIGHT, - zIndex: 1000, - elevation: 5, - shadowColor: '#000', - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.15, - shadowRadius: 6, - }, - option: { - paddingHorizontal: 12, - paddingVertical: 10, - borderBottomWidth: 1, - borderBottomColor: '#f0f0f0', - }, - optionSelected: { - backgroundColor: '#e8ecf8', - }, - optionText: { - fontSize: 14, - color: '#333', - }, - optionTextSelected: { - color: '#001A72', - fontWeight: '600', - }, -}); diff --git a/apps/computer-vision/components/ProgressBar.tsx b/apps/computer-vision/components/ProgressBar.tsx deleted file mode 100644 index fe2850de91..0000000000 --- a/apps/computer-vision/components/ProgressBar.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import { View, StyleSheet } from 'react-native'; -import ColorPalette from '../colors'; - -type ProgressBarProps = { - numSteps: number; - currentStep: number; -}; - -export default function ProgressBar({ - numSteps, - currentStep, -}: ProgressBarProps) { - return ( - - {Array.from({ length: numSteps }).map((_, i) => ( - - ))} - - ); -} - -const styles = StyleSheet.create({ - progressBarContainer: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - marginVertical: 16, - width: '80%', - }, - progressStep: { - flex: 1, - height: 15, - }, - progressStepActive: { - backgroundColor: ColorPalette.primary, - }, - progressStepInactive: { - backgroundColor: '#e0e0e0', - }, - progressStepFirst: { - borderTopLeftRadius: 8, - borderBottomLeftRadius: 8, - }, - progressStepLast: { - borderTopRightRadius: 8, - borderBottomRightRadius: 8, - }, -}); diff --git a/apps/computer-vision/components/Spinner.tsx b/apps/computer-vision/components/Spinner.tsx deleted file mode 100644 index f436869ab8..0000000000 --- a/apps/computer-vision/components/Spinner.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { View, Text, StyleSheet, ActivityIndicator, Modal } from 'react-native'; -import ColorPalette from '../colors'; - -interface SpinnerProps { - visible: boolean; - textContent: string; -} - -const Spinner = ({ visible, textContent }: SpinnerProps) => { - return ( - - - - - {textContent} - - - - ); -}; - -const styles = StyleSheet.create({ - overlay: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: 'rgba(0, 0, 0, 0.1)', - }, - container: { - padding: 25, - alignItems: 'center', - justifyContent: 'center', - }, - text: { - marginTop: 15, - color: ColorPalette.primary, - fontSize: 18, - fontWeight: 'bold', - }, -}); - -export default Spinner; diff --git a/apps/computer-vision/components/StatsBar.tsx b/apps/computer-vision/components/StatsBar.tsx deleted file mode 100644 index 621ecd21d1..0000000000 --- a/apps/computer-vision/components/StatsBar.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; - -interface Props { - inferenceTime: number | null; - detectionCount?: number | null; -} - -export function StatsBar({ inferenceTime, detectionCount }: Props) { - if (inferenceTime === null) return null; - - return ( - - Inference: {inferenceTime} ms - {detectionCount != null && ( - <> - · - - {detectionCount} detection{detectionCount !== 1 ? 's' : ''} - - - )} - - ); -} - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - gap: 8, - paddingVertical: 6, - }, - stat: { - fontSize: 13, - color: '#334155', - fontWeight: '500', - }, - separator: { - fontSize: 13, - color: '#94A3B8', - }, -}); diff --git a/apps/computer-vision/components/utils/cocoSkeleton.ts b/apps/computer-vision/components/utils/cocoSkeleton.ts deleted file mode 100644 index 9f1e051ab3..0000000000 --- a/apps/computer-vision/components/utils/cocoSkeleton.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const COCO_SKELETON_CONNECTIONS = [ - ['NOSE', 'LEFT_EYE'], - ['NOSE', 'RIGHT_EYE'], - ['LEFT_EYE', 'LEFT_EAR'], - ['RIGHT_EYE', 'RIGHT_EAR'], - ['LEFT_SHOULDER', 'RIGHT_SHOULDER'], - ['LEFT_SHOULDER', 'LEFT_ELBOW'], - ['LEFT_ELBOW', 'LEFT_WRIST'], - ['RIGHT_SHOULDER', 'RIGHT_ELBOW'], - ['RIGHT_ELBOW', 'RIGHT_WRIST'], - ['LEFT_SHOULDER', 'LEFT_HIP'], - ['RIGHT_SHOULDER', 'RIGHT_HIP'], - ['LEFT_HIP', 'RIGHT_HIP'], - ['LEFT_HIP', 'LEFT_KNEE'], - ['LEFT_KNEE', 'LEFT_ANKLE'], - ['RIGHT_HIP', 'RIGHT_KNEE'], - ['RIGHT_KNEE', 'RIGHT_ANKLE'], -] as const; diff --git a/apps/computer-vision/components/utils/colors.ts b/apps/computer-vision/components/utils/colors.ts deleted file mode 100644 index c38493a3b0..0000000000 --- a/apps/computer-vision/components/utils/colors.ts +++ /dev/null @@ -1,41 +0,0 @@ -export const CLASS_COLORS: number[][] = [ - [0, 0, 0, 0], - [51, 255, 87, 180], - [51, 87, 255, 180], - [255, 51, 246, 180], - [51, 255, 246, 180], - [243, 255, 51, 180], - [141, 51, 255, 180], - [255, 131, 51, 180], - [51, 255, 131, 180], - [131, 51, 255, 180], - [255, 255, 51, 180], - [51, 255, 255, 180], - [255, 51, 143, 180], - [127, 51, 255, 180], - [51, 255, 175, 180], - [255, 175, 51, 180], - [179, 255, 51, 180], - [255, 87, 51, 180], - [255, 51, 162, 180], - [51, 162, 255, 180], - [162, 51, 255, 180], -]; - -export function hashLabel(label: string): number { - let hash = 5381; - for (let i = 0; i < label.length; i++) { - hash = (hash + hash * 32 + label.charCodeAt(i)) % 1000003; - } - return 1 + (Math.abs(hash) % (CLASS_COLORS.length - 1)); -} - -export function labelColor(label: string): string { - const color = CLASS_COLORS[hashLabel(label)]!; - return `rgba(${color[0]},${color[1]},${color[2]},1)`; -} - -export function labelColorBg(label: string): string { - const color = CLASS_COLORS[hashLabel(label)]!; - return `rgba(${color[0]},${color[1]},${color[2]},0.75)`; -} diff --git a/apps/computer-vision/components/vision_camera/tasks/ClassificationTask.tsx b/apps/computer-vision/components/vision_camera/tasks/ClassificationTask.tsx deleted file mode 100644 index f108bb4aa5..0000000000 --- a/apps/computer-vision/components/vision_camera/tasks/ClassificationTask.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { StyleSheet, Text, View } from 'react-native'; -import { Frame, useFrameOutput } from 'react-native-vision-camera'; -import { scheduleOnRN } from 'react-native-worklets'; -import { models, useClassification } from 'react-native-executorch'; -import { FRAME_TARGET_RESOLUTION, TaskProps } from './types'; - -type Props = Omit< - TaskProps, - 'activeModel' | 'canvasSize' | 'cameraPositionSync' ->; - -export default function ClassificationTask({ - frameKillSwitch, - onFrameOutputChange, - onReadyChange, - onProgressChange, - onGeneratingChange, - onFpsChange, - onErrorChange, -}: Props) { - const model = useClassification({ - model: models.classification.efficientnet_v2_s(), - }); - const [classResult, setClassResult] = useState({ label: '', score: 0 }); - const lastFrameTimeRef = useRef(Date.now()); - - useEffect(() => { - onErrorChange(model.error ? String(model.error) : null); - }, [model.error, onErrorChange]); - - useEffect(() => { - onReadyChange(model.isReady); - }, [model.isReady, onReadyChange]); - - useEffect(() => { - onProgressChange(model.downloadProgress); - }, [model.downloadProgress, onProgressChange]); - - useEffect(() => { - onGeneratingChange(model.isGenerating); - }, [model.isGenerating, onGeneratingChange]); - - const classRof = model.runOnFrame; - - const updateClass = useCallback( - (r: { label: string; score: number }) => { - setClassResult(r); - const now = Date.now(); - const diff = now - lastFrameTimeRef.current; - if (diff > 0) onFpsChange(Math.round(1000 / diff), diff); - lastFrameTimeRef.current = now; - }, - [onFpsChange] - ); - - const frameOutput = useFrameOutput({ - targetResolution: FRAME_TARGET_RESOLUTION, - pixelFormat: 'rgb', - dropFramesWhileBusy: true, - enablePreviewSizedOutputBuffers: true, - onFrame: useCallback( - (frame: Frame) => { - 'worklet'; - if (frameKillSwitch.getDirty()) { - frame.dispose(); - return; - } - try { - if (!classRof) return; - const result = classRof(frame); - if (result) { - let bestLabel = ''; - let bestScore = -1; - const entries = Object.entries(result); - for (let i = 0; i < entries.length; i++) { - const [label, score] = entries[i]!; - if ((score as number) > bestScore) { - bestScore = score as number; - bestLabel = label; - } - } - scheduleOnRN(updateClass, { label: bestLabel, score: bestScore }); - } - } catch { - // Frame may be disposed before processing completes — transient, safe to ignore. - } finally { - frame.dispose(); - } - }, - [classRof, frameKillSwitch, updateClass] - ), - }); - - useEffect(() => { - onFrameOutputChange(frameOutput); - }, [frameOutput, onFrameOutputChange]); - - return classResult.label ? ( - - {classResult.label} - {(classResult.score * 100).toFixed(1)}% - - ) : null; -} - -const styles = StyleSheet.create({ - overlay: { - ...StyleSheet.absoluteFill, - justifyContent: 'center', - alignItems: 'center', - }, - label: { - color: 'white', - fontSize: 28, - fontWeight: '700', - textAlign: 'center', - textShadowColor: 'rgba(0,0,0,0.8)', - textShadowOffset: { width: 0, height: 1 }, - textShadowRadius: 6, - paddingHorizontal: 24, - }, - score: { - color: 'rgba(255,255,255,0.75)', - fontSize: 18, - fontWeight: '500', - marginTop: 4, - textShadowColor: 'rgba(0,0,0,0.8)', - textShadowOffset: { width: 0, height: 1 }, - textShadowRadius: 6, - }, -}); diff --git a/apps/computer-vision/components/vision_camera/tasks/InstanceSegmentationTask.tsx b/apps/computer-vision/components/vision_camera/tasks/InstanceSegmentationTask.tsx deleted file mode 100644 index cd203eeb66..0000000000 --- a/apps/computer-vision/components/vision_camera/tasks/InstanceSegmentationTask.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { StyleSheet, Text, View } from 'react-native'; -import { Frame, useFrameOutput } from 'react-native-vision-camera'; -import { scheduleOnRN } from 'react-native-worklets'; -import { - models, - SegmentedInstance, - useInstanceSegmentation, - CocoLabel, - CocoLabelYolo, - FastSAMLabel, -} from 'react-native-executorch'; -import { Canvas, Image as SkiaImage } from '@shopify/react-native-skia'; -import { labelColor, labelColorBg } from '../../utils/colors'; -import { FRAME_TARGET_RESOLUTION, TaskProps } from './types'; -import { - buildDisplayInstances, - DisplayInstance, -} from '../../../components/ImageWithMasks'; -const instanceSegmentation = models.instance_segmentation; - -type InstSegModelId = - | 'instanceSegmentationYolo26n' - | 'instanceSegmentationRfdetr' - | 'instanceSegmentationFastsamS' - | 'instanceSegmentationFastsamX'; - -type Props = TaskProps & { activeModel: InstSegModelId }; - -export default function InstanceSegmentationTask({ - activeModel, - canvasSize, - cameraPositionSync, - frameKillSwitch, - onFrameOutputChange, - onReadyChange, - onProgressChange, - onGeneratingChange, - onFpsChange, - onErrorChange, -}: Props) { - const yolo26n = useInstanceSegmentation({ - model: instanceSegmentation.yolo26n(), - preventLoad: activeModel !== 'instanceSegmentationYolo26n', - }); - const rfdetr = useInstanceSegmentation({ - model: instanceSegmentation.rf_detr_nano(), - preventLoad: activeModel !== 'instanceSegmentationRfdetr', - }); - const fastsamS = useInstanceSegmentation({ - model: instanceSegmentation.fastsam_s(), - preventLoad: activeModel !== 'instanceSegmentationFastsamS', - }); - const fastsamX = useInstanceSegmentation({ - model: instanceSegmentation.fastsam_x(), - preventLoad: activeModel !== 'instanceSegmentationFastsamX', - }); - - const active = { - instanceSegmentationYolo26n: yolo26n, - instanceSegmentationRfdetr: rfdetr, - instanceSegmentationFastsamS: fastsamS, - instanceSegmentationFastsamX: fastsamX, - }[activeModel]; - - const [instances, setInstances] = useState([]); - const [imageSize, setImageSize] = useState({ width: 1, height: 1 }); - const lastFrameTimeRef = useRef(Date.now()); - - useEffect(() => { - onErrorChange(active.error ? String(active.error) : null); - }, [active.error, onErrorChange]); - - useEffect(() => { - onReadyChange(active.isReady); - }, [active.isReady, onReadyChange]); - - useEffect(() => { - onProgressChange(active.downloadProgress); - }, [active.downloadProgress, onProgressChange]); - - useEffect(() => { - onGeneratingChange(active.isGenerating); - }, [active.isGenerating, onGeneratingChange]); - - const instSegRof = active.runOnFrame; - - const updateInstances = useCallback( - (p: { - results: - | SegmentedInstance[] - | SegmentedInstance[] - | SegmentedInstance[]; - imageWidth: number; - imageHeight: number; - }) => { - const displayInstances = buildDisplayInstances( - p.results.map((inst) => ({ - ...inst, - label: String(inst.label), - })) - ); - setInstances((prev) => { - // Dispose old mask images - prev.forEach((inst) => inst.maskImage.dispose()); - return displayInstances; - }); - setImageSize({ width: p.imageWidth, height: p.imageHeight }); - const now = Date.now(); - const diff = now - lastFrameTimeRef.current; - if (diff > 0) onFpsChange(Math.round(1000 / diff), diff); - lastFrameTimeRef.current = now; - }, - [onFpsChange] - ); - - const frameOutput = useFrameOutput({ - targetResolution: FRAME_TARGET_RESOLUTION, - pixelFormat: 'rgb', - dropFramesWhileBusy: true, - enablePreviewSizedOutputBuffers: true, - onFrame: useCallback( - (frame: Frame) => { - 'worklet'; - if (frameKillSwitch.getDirty()) { - frame.dispose(); - return; - } - try { - if (!instSegRof) return; - const isFrontCamera = cameraPositionSync.getDirty() === 'front'; - const iw = frame.width > frame.height ? frame.height : frame.width; - const ih = frame.width > frame.height ? frame.width : frame.height; - const result = instSegRof(frame, isFrontCamera, { - confidenceThreshold: 0.5, - iouThreshold: 0.5, - maxInstances: 5, - returnMaskAtOriginalResolution: false, - ...(activeModel === 'instanceSegmentationYolo26n' && { - inputSize: 384, - }), - }); - if (result) { - scheduleOnRN(updateInstances, { - results: result, - imageWidth: iw, - imageHeight: ih, - }); - } - } catch { - // ignore - } finally { - frame.dispose(); - } - }, - [ - instSegRof, - frameKillSwitch, - updateInstances, - activeModel, - cameraPositionSync, - ] - ), - }); - - useEffect(() => { - onFrameOutputChange(frameOutput); - }, [frameOutput, onFrameOutputChange]); - - const scale = Math.max( - canvasSize.width / imageSize.width, - canvasSize.height / imageSize.height - ); - const offsetX = (canvasSize.width - imageSize.width * scale) / 2; - const offsetY = (canvasSize.height - imageSize.height * scale) / 2; - - return ( - - {/* Render masks */} - - {instances.map((inst, i) => { - const x = inst.bbox.x1 * scale + offsetX; - const y = inst.bbox.y1 * scale + offsetY; - const w = (inst.bbox.x2 - inst.bbox.x1) * scale; - const h = (inst.bbox.y2 - inst.bbox.y1) * scale; - return ( - - ); - })} - - {/* Render bounding boxes */} - {instances.map((inst, i) => { - const left = inst.bbox.x1 * scale + offsetX; - const top = inst.bbox.y1 * scale + offsetY; - const w = (inst.bbox.x2 - inst.bbox.x1) * scale; - const h = (inst.bbox.y2 - inst.bbox.y1) * scale; - const label = String(inst.label); - return ( - - - - {label} {(inst.score * 100).toFixed(1)} - - - - ); - })} - - ); -} - -const styles = StyleSheet.create({ - bbox: { - position: 'absolute', - borderWidth: 2, - borderColor: 'cyan', - borderRadius: 4, - }, - bboxLabel: { - position: 'absolute', - top: -22, - left: -2, - paddingHorizontal: 6, - paddingVertical: 2, - borderRadius: 4, - }, - bboxLabelText: { color: 'white', fontSize: 11, fontWeight: '600' }, -}); diff --git a/apps/computer-vision/components/vision_camera/tasks/OCRTask.tsx b/apps/computer-vision/components/vision_camera/tasks/OCRTask.tsx deleted file mode 100644 index 54e73161a1..0000000000 --- a/apps/computer-vision/components/vision_camera/tasks/OCRTask.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Frame, useFrameOutput } from 'react-native-vision-camera'; -import { scheduleOnRN } from 'react-native-worklets'; -import { models, OCRDetection, useOCR } from 'react-native-executorch'; -import Svg, { Polygon, Text as SvgText } from 'react-native-svg'; -import { FRAME_TARGET_RESOLUTION, TaskProps } from './types'; - -type Props = Omit; - -export default function OCRTask({ - canvasSize, - cameraPositionSync, - frameKillSwitch, - onFrameOutputChange, - onReadyChange, - onProgressChange, - onGeneratingChange, - onFpsChange, - onErrorChange, -}: Props) { - const model = useOCR({ model: models.ocr.craft({ language: 'en' }) }); - const [detections, setDetections] = useState([]); - const [imageSize, setImageSize] = useState({ width: 1, height: 1 }); - const lastFrameTimeRef = useRef(Date.now()); - - useEffect(() => { - onErrorChange(model.error ? String(model.error) : null); - }, [model.error, onErrorChange]); - - useEffect(() => { - onReadyChange(model.isReady); - }, [model.isReady, onReadyChange]); - - useEffect(() => { - onProgressChange(model.downloadProgress); - }, [model.downloadProgress, onProgressChange]); - - useEffect(() => { - onGeneratingChange(model.isGenerating); - }, [model.isGenerating, onGeneratingChange]); - - const ocrRof = model.runOnFrame; - - const updateDetections = useCallback( - (p: { results: OCRDetection[]; frameW: number; frameH: number }) => { - setDetections(p.results); - setImageSize({ width: p.frameW, height: p.frameH }); - const now = Date.now(); - const diff = now - lastFrameTimeRef.current; - if (diff > 0) onFpsChange(Math.round(1000 / diff), diff); - lastFrameTimeRef.current = now; - }, - [onFpsChange] - ); - - const frameOutput = useFrameOutput({ - targetResolution: FRAME_TARGET_RESOLUTION, - pixelFormat: 'rgb', - dropFramesWhileBusy: true, - enablePreviewSizedOutputBuffers: true, - onFrame: useCallback( - (frame: Frame) => { - 'worklet'; - if (frameKillSwitch.getDirty()) { - frame.dispose(); - return; - } - try { - if (!ocrRof) return; - const isFrontCamera = cameraPositionSync.getDirty() === 'front'; - const result = ocrRof(frame, isFrontCamera); - if (result) { - // Sensor frames are landscape-native, so width/height are swapped - // relative to portrait screen orientation. - scheduleOnRN(updateDetections, { - results: result, - frameW: frame.height, - frameH: frame.width, - }); - } - } catch { - // Frame may be disposed before processing completes — transient, safe to ignore. - } finally { - frame.dispose(); - } - }, - [cameraPositionSync, frameKillSwitch, ocrRof, updateDetections] - ), - }); - - useEffect(() => { - onFrameOutputChange(frameOutput); - }, [frameOutput, onFrameOutputChange]); - - const scale = Math.max( - canvasSize.width / imageSize.width, - canvasSize.height / imageSize.height - ); - const offsetX = (canvasSize.width - imageSize.width * scale) / 2; - const offsetY = (canvasSize.height - imageSize.height * scale) / 2; - - if (!detections.length) return null; - - return ( - - - {detections.map((det, i) => { - const { x1, y1, x2, y2 } = det.bbox; - const pts = [ - [x1, y1], - [x2, y1], - [x2, y2], - [x1, y2], - ] - .map(([x, y]) => `${x * scale + offsetX},${y * scale + offsetY}`) - .join(' '); - const labelX = x1 * scale + offsetX; - const labelY = y1 * scale + offsetY - 4; - return ( - - - - {det.text} - - - ); - })} - - - ); -} diff --git a/apps/computer-vision/components/vision_camera/tasks/ObjectDetectionTask.tsx b/apps/computer-vision/components/vision_camera/tasks/ObjectDetectionTask.tsx deleted file mode 100644 index e05de26105..0000000000 --- a/apps/computer-vision/components/vision_camera/tasks/ObjectDetectionTask.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Frame, useFrameOutput } from 'react-native-vision-camera'; -import { scheduleOnRN } from 'react-native-worklets'; -import { - models, - Detection, - useObjectDetection, - CocoLabel, - CocoLabelYolo, -} from 'react-native-executorch'; -import BoundingBoxes from '../../BoundingBoxes'; -import { FRAME_TARGET_RESOLUTION, TaskProps } from './types'; -const objectDetection = models.object_detection; - -type ObjModelId = - | 'objectDetectionSsdlite' - | 'objectDetectionRfdetr' - | 'objectDetectionYolo26n'; - -type Props = TaskProps & { activeModel: ObjModelId }; - -export default function ObjectDetectionTask({ - activeModel, - canvasSize, - cameraPositionSync, - frameKillSwitch, - onFrameOutputChange, - onReadyChange, - onProgressChange, - onGeneratingChange, - onFpsChange, - onErrorChange, -}: Props) { - const ssdlite = useObjectDetection({ - model: objectDetection.ssdlite_320_mobilenet_v3_large(), - preventLoad: activeModel !== 'objectDetectionSsdlite', - }); - const rfdetr = useObjectDetection({ - model: objectDetection.rf_detr_nano(), - preventLoad: activeModel !== 'objectDetectionRfdetr', - }); - const yolo26n = useObjectDetection({ - model: objectDetection.yolo26n(), - preventLoad: activeModel !== 'objectDetectionYolo26n', - }); - - const active = - activeModel === 'objectDetectionSsdlite' - ? ssdlite - : activeModel === 'objectDetectionRfdetr' - ? rfdetr - : yolo26n; - - type CommonDetection = Omit & { label: string }; - - const [detections, setDetections] = useState([]); - const [imageSize, setImageSize] = useState({ width: 1, height: 1 }); - const lastFrameTimeRef = useRef(Date.now()); - - useEffect(() => { - onErrorChange(active.error ? String(active.error) : null); - }, [active.error, onErrorChange]); - - useEffect(() => { - onReadyChange(active.isReady); - }, [active.isReady, onReadyChange]); - - useEffect(() => { - onProgressChange(active.downloadProgress); - }, [active.downloadProgress, onProgressChange]); - - useEffect(() => { - onGeneratingChange(active.isGenerating); - }, [active.isGenerating, onGeneratingChange]); - - const detRof = active.runOnFrame; - - const updateDetections = useCallback( - (p: { - results: - | Detection[] - | Detection[]; - imageWidth: number; - imageHeight: number; - }) => { - setDetections( - p.results.map((det) => ({ - ...det, - label: String(det.label), - })) - ); - setImageSize({ width: p.imageWidth, height: p.imageHeight }); - const now = Date.now(); - const diff = now - lastFrameTimeRef.current; - if (diff > 0) onFpsChange(Math.round(1000 / diff), diff); - lastFrameTimeRef.current = now; - }, - [onFpsChange] - ); - - const frameOutput = useFrameOutput({ - targetResolution: FRAME_TARGET_RESOLUTION, - pixelFormat: 'rgb', - dropFramesWhileBusy: true, - enablePreviewSizedOutputBuffers: true, - - onFrame: useCallback( - (frame: Frame) => { - 'worklet'; - if (frameKillSwitch.getDirty()) { - frame.dispose(); - return; - } - try { - if (!detRof) return; - const isFrontCamera = cameraPositionSync.getDirty() === 'front'; - const result = detRof(frame, isFrontCamera, { - detectionThreshold: 0.5, - }); - // Sensor frames are landscape-native, so width/height are swapped - // relative to portrait screen orientation. - const screenW = frame.height; - const screenH = frame.width; - if (result) { - scheduleOnRN(updateDetections, { - results: result, - imageWidth: screenW, - imageHeight: screenH, - }); - } - } catch { - // Frame may be disposed before processing completes — transient, safe to ignore. - } finally { - frame.dispose(); - } - }, - [cameraPositionSync, detRof, frameKillSwitch, updateDetections] - ), - }); - - useEffect(() => { - onFrameOutputChange(frameOutput); - }, [frameOutput, onFrameOutputChange]); - - const scale = Math.max( - canvasSize.width / imageSize.width, - canvasSize.height / imageSize.height - ); - const offsetX = (canvasSize.width - imageSize.width * scale) / 2; - const offsetY = (canvasSize.height - imageSize.height * scale) / 2; - - return ( - - - - ); -} diff --git a/apps/computer-vision/components/vision_camera/tasks/PoseEstimationTask.tsx b/apps/computer-vision/components/vision_camera/tasks/PoseEstimationTask.tsx deleted file mode 100644 index a357881da0..0000000000 --- a/apps/computer-vision/components/vision_camera/tasks/PoseEstimationTask.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Frame, useFrameOutput } from 'react-native-vision-camera'; -import { scheduleOnRN } from 'react-native-worklets'; -import Svg, { Circle, Line } from 'react-native-svg'; -import { - models, - usePoseEstimation, - PoseDetections, -} from 'react-native-executorch'; -import { TaskProps } from './types'; -import { COCO_SKELETON_CONNECTIONS } from '../../utils/cocoSkeleton'; - -type Props = TaskProps & { activeModel: 'poseEstimationYolo26n' }; - -// Colors for different people -const PERSON_COLORS = ['lime', 'cyan', 'magenta', 'yellow', 'orange', 'pink']; - -export default function PoseEstimationTask({ - activeModel, - canvasSize, - cameraPositionSync, - frameKillSwitch, - onFrameOutputChange, - onReadyChange, - onProgressChange, - onGeneratingChange, - onFpsChange, - onErrorChange, -}: Props) { - const poseModel = usePoseEstimation({ - model: models.pose_estimation.yolo26n(), - preventLoad: activeModel !== 'poseEstimationYolo26n', - }); - - const [detections, setDetections] = useState([]); - const [imageSize, setImageSize] = useState({ width: 1, height: 1 }); - const lastFrameTimeRef = useRef(Date.now()); - - useEffect(() => { - onErrorChange(poseModel.error ? String(poseModel.error) : null); - }, [poseModel.error, onErrorChange]); - - useEffect(() => { - onReadyChange(poseModel.isReady); - }, [poseModel.isReady, onReadyChange]); - - useEffect(() => { - onProgressChange(poseModel.downloadProgress); - }, [poseModel.downloadProgress, onProgressChange]); - - useEffect(() => { - onGeneratingChange(poseModel.isGenerating); - }, [poseModel.isGenerating, onGeneratingChange]); - - const poseRof = poseModel.runOnFrame; - - const updateDetections = useCallback( - (p: { - results: PoseDetections; - imageWidth: number; - imageHeight: number; - }) => { - setDetections(p.results); - setImageSize({ width: p.imageWidth, height: p.imageHeight }); - const now = Date.now(); - const diff = now - lastFrameTimeRef.current; - if (diff > 0) onFpsChange(Math.round(1000 / diff), diff); - lastFrameTimeRef.current = now; - }, - [onFpsChange] - ); - - const frameOutput = useFrameOutput({ - pixelFormat: 'rgb', - dropFramesWhileBusy: true, - enablePreviewSizedOutputBuffers: true, - - onFrame: useCallback( - (frame: Frame) => { - 'worklet'; - if (frameKillSwitch.getDirty()) { - frame.dispose(); - return; - } - try { - if (!poseRof) return; - const isFrontCamera = cameraPositionSync.getDirty() === 'front'; - const result = poseRof(frame, isFrontCamera, { - detectionThreshold: 0.5, - }); - const screenW = frame.height; - const screenH = frame.width; - if (result) { - scheduleOnRN(updateDetections, { - results: result, - imageWidth: screenW, - imageHeight: screenH, - }); - } - } catch { - // Frame may be disposed before processing completes - } finally { - frame.dispose(); - } - }, - [cameraPositionSync, poseRof, frameKillSwitch, updateDetections] - ), - }); - - useEffect(() => { - onFrameOutputChange(frameOutput); - }, [frameOutput, onFrameOutputChange]); - - const scale = Math.max( - canvasSize.width / imageSize.width, - canvasSize.height / imageSize.height - ); - const offsetX = (canvasSize.width - imageSize.width * scale) / 2; - const offsetY = (canvasSize.height - imageSize.height * scale) / 2; - - return ( - - - {detections.map((personKeypoints, personIdx) => { - const color = PERSON_COLORS[personIdx % PERSON_COLORS.length]; - const isVisible = (kp: { x: number; y: number }) => - kp.x >= 0 && - kp.y >= 0 && - kp.x <= imageSize.width && - kp.y <= imageSize.height; - return ( - - {/* Draw skeleton lines */} - {COCO_SKELETON_CONNECTIONS.map(([from, to], lineIdx) => { - const kp1 = personKeypoints[from]; - const kp2 = personKeypoints[to]; - if (!kp1 || !kp2) return null; - if (!isVisible(kp1) || !isVisible(kp2)) return null; - const x1 = kp1.x * scale + offsetX; - const y1 = kp1.y * scale + offsetY; - const x2 = kp2.x * scale + offsetX; - const y2 = kp2.y * scale + offsetY; - return ( - - ); - })} - {/* Draw keypoints */} - {Object.entries(personKeypoints) - .filter(([, kp]) => isVisible(kp)) - .map(([name, kp]) => { - const cx = kp.x * scale + offsetX; - const cy = kp.y * scale + offsetY; - return ( - - ); - })} - - ); - })} - - - ); -} diff --git a/apps/computer-vision/components/vision_camera/tasks/SegmentationTask.tsx b/apps/computer-vision/components/vision_camera/tasks/SegmentationTask.tsx deleted file mode 100644 index 8c499d977a..0000000000 --- a/apps/computer-vision/components/vision_camera/tasks/SegmentationTask.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Frame, useFrameOutput } from 'react-native-vision-camera'; -import { scheduleOnRN } from 'react-native-worklets'; -import { models, useSemanticSegmentation } from 'react-native-executorch'; -import { - AlphaType, - Canvas, - ColorType, - Image as SkiaImage, - Skia, - SkImage, -} from '@shopify/react-native-skia'; -import { CLASS_COLORS } from '../../utils/colors'; -import { FRAME_TARGET_RESOLUTION, TaskProps } from './types'; -const semanticSegmentation = models.semantic_segmentation; - -type SegModelId = - | 'segmentationDeeplabResnet50' - | 'segmentationDeeplabResnet101' - | 'segmentationDeeplabMobilenet' - | 'segmentationLraspp' - | 'segmentationFcnResnet50' - | 'segmentationFcnResnet101' - | 'segmentationSelfie'; - -type Props = TaskProps & { activeModel: SegModelId }; - -export default function SegmentationTask({ - activeModel, - canvasSize, - cameraPositionSync, - frameKillSwitch, - onFrameOutputChange, - onReadyChange, - onProgressChange, - onGeneratingChange, - onFpsChange, - onErrorChange, -}: Props) { - const segDeeplabResnet50 = useSemanticSegmentation({ - model: semanticSegmentation.deeplab_v3_resnet50(), - preventLoad: activeModel !== 'segmentationDeeplabResnet50', - }); - const segDeeplabResnet101 = useSemanticSegmentation({ - model: semanticSegmentation.deeplab_v3_resnet101(), - preventLoad: activeModel !== 'segmentationDeeplabResnet101', - }); - const segDeeplabMobilenet = useSemanticSegmentation({ - model: semanticSegmentation.deeplab_v3_mobilenet_v3_large(), - preventLoad: activeModel !== 'segmentationDeeplabMobilenet', - }); - const segLraspp = useSemanticSegmentation({ - model: semanticSegmentation.lraspp_mobilenet_v3_large(), - preventLoad: activeModel !== 'segmentationLraspp', - }); - const segFcnResnet50 = useSemanticSegmentation({ - model: semanticSegmentation.fcn_resnet50(), - preventLoad: activeModel !== 'segmentationFcnResnet50', - }); - const segFcnResnet101 = useSemanticSegmentation({ - model: semanticSegmentation.fcn_resnet101(), - preventLoad: activeModel !== 'segmentationFcnResnet101', - }); - const segSelfie = useSemanticSegmentation({ - model: semanticSegmentation.selfie_segmentation(), - preventLoad: activeModel !== 'segmentationSelfie', - }); - - const active = { - segmentationDeeplabResnet50: segDeeplabResnet50, - segmentationDeeplabResnet101: segDeeplabResnet101, - segmentationDeeplabMobilenet: segDeeplabMobilenet, - segmentationLraspp: segLraspp, - segmentationFcnResnet50: segFcnResnet50, - segmentationFcnResnet101: segFcnResnet101, - segmentationSelfie: segSelfie, - }[activeModel]; - - const [maskImage, setMaskImage] = useState(null); - const [imageSize, setImageSize] = useState({ width: 1, height: 1 }); - const lastFrameTimeRef = useRef(Date.now()); - - useEffect(() => { - onErrorChange(active.error ? String(active.error) : null); - }, [active.error, onErrorChange]); - - useEffect(() => { - onReadyChange(active.isReady); - }, [active.isReady, onReadyChange]); - - useEffect(() => { - onProgressChange(active.downloadProgress); - }, [active.downloadProgress, onProgressChange]); - - useEffect(() => { - onGeneratingChange(active.isGenerating); - }, [active.isGenerating, onGeneratingChange]); - - // Clear stale mask when the segmentation model variant changes - useEffect(() => { - setMaskImage((prev) => { - prev?.dispose(); - return null; - }); - }, [activeModel]); - - // Dispose native Skia image on unmount to prevent memory leaks - useEffect(() => { - return () => { - setMaskImage((prev) => { - prev?.dispose(); - return null; - }); - }; - }, []); - - const segRof = active.runOnFrame; - - const updateMask = useCallback( - (p: { img: SkImage; screenW: number; screenH: number }) => { - setMaskImage((prev) => { - prev?.dispose(); - return p.img; - }); - setImageSize({ width: p.screenW, height: p.screenH }); - const now = Date.now(); - const diff = now - lastFrameTimeRef.current; - if (diff > 0) onFpsChange(Math.round(1000 / diff), diff); - lastFrameTimeRef.current = now; - }, - [onFpsChange] - ); - - // CLASS_COLORS captured directly in closure — worklets cannot import modules - const colors = CLASS_COLORS; - - const frameOutput = useFrameOutput({ - targetResolution: FRAME_TARGET_RESOLUTION, - pixelFormat: 'rgb', - dropFramesWhileBusy: true, - enablePreviewSizedOutputBuffers: true, - onFrame: useCallback( - (frame: Frame) => { - 'worklet'; - if (frameKillSwitch.getDirty()) { - frame.dispose(); - return; - } - try { - if (!segRof) return; - const isFrontCamera = cameraPositionSync.getDirty() === 'front'; - const result = segRof(frame, isFrontCamera, [], false); - if (result?.ARGMAX) { - const argmax: Int32Array = result.ARGMAX; - // Both the preview and the mask live in a portrait coord system: - // the activity is portrait-locked (so CameraX's PreviewView always - // renders the preview in portrait orientation regardless of how - // the device is physically tilted), and the native side runs - // `inverseRotateMat` which always converts the mask into the same - // portrait coord system. Treat sensor-native dims as portrait by - // swapping height/width — same convention as the sibling OCR and - // ObjectDetection tasks. - const screenW = frame.height; - const screenH = frame.width; - // Mask buffer dims: the C++ side returns the mask at model output - // resolution (the `resizeToInput=false` arg below). All built-in - // segmentation models output a square spatial map (e.g. 520×520), - // so sqrt(length) recovers the side. Non-square model outputs - // would need dims exposed from native. - const maskSide = Math.round(Math.sqrt(argmax.length)); - const maskW = maskSide; - const maskH = maskSide; - const pixels = new Uint8Array(maskW * maskH * 4); - for (let i = 0; i < argmax.length; i++) { - const color = colors[argmax[i]!] ?? [0, 0, 0, 0]; - pixels[i * 4] = color[0]!; - pixels[i * 4 + 1] = color[1]!; - pixels[i * 4 + 2] = color[2]!; - pixels[i * 4 + 3] = color[3]!; - } - const skData = Skia.Data.fromBytes(pixels); - const img = Skia.Image.MakeImage( - { - width: maskW, - height: maskH, - alphaType: AlphaType.Unpremul, - colorType: ColorType.RGBA_8888, - }, - skData, - maskW * 4 - ); - if (img) scheduleOnRN(updateMask, { img, screenW, screenH }); - } - } catch { - // Frame may be disposed before processing completes — transient, safe to ignore. - } finally { - frame.dispose(); - } - }, - [cameraPositionSync, colors, frameKillSwitch, segRof, updateMask] - ), - }); - - useEffect(() => { - onFrameOutputChange(frameOutput); - }, [frameOutput, onFrameOutputChange]); - - if (!maskImage) return null; - - // Match the camera preview's cover-scale + center layout so the mask - // aligns pixel-for-pixel with what the user sees. `fit="fill"` lets the - // (square) mask stretch into the preview rect — which is computed in - // screen-space dims rather than the sensor-native ones. - const scale = Math.max( - canvasSize.width / imageSize.width, - canvasSize.height / imageSize.height - ); - const dstW = imageSize.width * scale; - const dstH = imageSize.height * scale; - const offsetX = (canvasSize.width - dstW) / 2; - const offsetY = (canvasSize.height - dstH) / 2; - - return ( - - - - - - ); -} diff --git a/apps/computer-vision/components/vision_camera/tasks/StyleTransferTask.tsx b/apps/computer-vision/components/vision_camera/tasks/StyleTransferTask.tsx deleted file mode 100644 index d5f8578272..0000000000 --- a/apps/computer-vision/components/vision_camera/tasks/StyleTransferTask.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { StyleSheet, View } from 'react-native'; -import { Frame, useFrameOutput } from 'react-native-vision-camera'; -import { scheduleOnRN } from 'react-native-worklets'; -import { models, useStyleTransfer } from 'react-native-executorch'; -import { - AlphaType, - Canvas, - ColorType, - Image as SkiaImage, - Skia, - SkImage, -} from '@shopify/react-native-skia'; -import { FRAME_TARGET_RESOLUTION, TaskProps } from './types'; -const styleTransfer = models.style_transfer; - -type StyleModelId = 'styleTransferCandy' | 'styleTransferMosaic'; - -type Props = TaskProps & { activeModel: StyleModelId }; - -export default function StyleTransferTask({ - activeModel, - canvasSize, - cameraPositionSync, - frameKillSwitch, - onFrameOutputChange, - onReadyChange, - onProgressChange, - onGeneratingChange, - onFpsChange, - onErrorChange, -}: Props) { - const candy = useStyleTransfer({ - model: styleTransfer.candy(), - preventLoad: activeModel !== 'styleTransferCandy', - }); - const mosaic = useStyleTransfer({ - model: styleTransfer.mosaic(), - preventLoad: activeModel !== 'styleTransferMosaic', - }); - - const active = activeModel === 'styleTransferCandy' ? candy : mosaic; - - const [styledImage, setStyledImage] = useState(null); - const lastFrameTimeRef = useRef(Date.now()); - - useEffect(() => { - onErrorChange(active.error ? String(active.error) : null); - }, [active.error, onErrorChange]); - - useEffect(() => { - onReadyChange(active.isReady); - }, [active.isReady, onReadyChange]); - - useEffect(() => { - onProgressChange(active.downloadProgress); - }, [active.downloadProgress, onProgressChange]); - - useEffect(() => { - onGeneratingChange(active.isGenerating); - }, [active.isGenerating, onGeneratingChange]); - - useEffect(() => { - setStyledImage((prev) => { - prev?.dispose(); - return null; - }); - }, [activeModel]); - - useEffect(() => { - return () => { - setStyledImage((prev) => { - prev?.dispose(); - return null; - }); - }; - }, []); - - const styleRof = active.runOnFrame; - - const updateImage = useCallback( - (img: SkImage) => { - setStyledImage((prev) => { - prev?.dispose(); - return img; - }); - const now = Date.now(); - const diff = now - lastFrameTimeRef.current; - if (diff > 0) onFpsChange(Math.round(1000 / diff), diff); - lastFrameTimeRef.current = now; - }, - [onFpsChange] - ); - - const frameOutput = useFrameOutput({ - targetResolution: FRAME_TARGET_RESOLUTION, - pixelFormat: 'rgb', - dropFramesWhileBusy: true, - enablePreviewSizedOutputBuffers: true, - onFrame: useCallback( - (frame: Frame) => { - 'worklet'; - if (frameKillSwitch.getDirty()) { - frame.dispose(); - return; - } - try { - if (!styleRof) return; - const isFrontCamera = cameraPositionSync.getDirty() === 'front'; - const result = styleRof(frame, isFrontCamera); - if (result) { - const pixels = new Uint8Array(result.dataPtr.buffer); - const skData = Skia.Data.fromBytes(pixels); - const img = Skia.Image.MakeImage( - { - width: result.sizes[1]!, - height: result.sizes[0]!, - alphaType: AlphaType.Unpremul, - colorType: ColorType.RGBA_8888, - }, - skData, - result.sizes[1]! * 4 - ); - if (img) scheduleOnRN(updateImage, img); - } - } catch { - // Frame may be disposed before processing completes — transient, safe to ignore. - } finally { - frame.dispose(); - } - }, - [cameraPositionSync, frameKillSwitch, styleRof, updateImage] - ), - }); - - useEffect(() => { - onFrameOutputChange(frameOutput); - }, [frameOutput, onFrameOutputChange]); - - if (!styledImage) return null; - - return ( - - - - - - ); -} diff --git a/apps/computer-vision/components/vision_camera/tasks/types.ts b/apps/computer-vision/components/vision_camera/tasks/types.ts deleted file mode 100644 index 53aca25570..0000000000 --- a/apps/computer-vision/components/vision_camera/tasks/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useFrameOutput } from 'react-native-vision-camera'; -import { createSynchronizable } from 'react-native-worklets'; - -export const FRAME_TARGET_RESOLUTION = { width: 1280, height: 720 } as const; - -export type TaskProps = { - activeModel: string; - canvasSize: { width: number; height: number }; - cameraPositionSync: ReturnType>; - frameKillSwitch: ReturnType>; - onFrameOutputChange: (frameOutput: ReturnType) => void; - onReadyChange: (isReady: boolean) => void; - onProgressChange: (progress: number) => void; - onGeneratingChange: (isGenerating: boolean) => void; - onFpsChange: (fps: number, frameMs: number) => void; - onErrorChange: (error: string | null) => void; -}; diff --git a/apps/computer-vision/context.ts b/apps/computer-vision/context.ts deleted file mode 100644 index 0eb0560768..0000000000 --- a/apps/computer-vision/context.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createContext } from 'react'; - -export const GeneratingContext = createContext({ - setGlobalGenerating: (_newState: boolean) => {}, -}); diff --git a/apps/computer-vision/declarations.d.ts b/apps/computer-vision/declarations.d.ts deleted file mode 100644 index 85e178f497..0000000000 --- a/apps/computer-vision/declarations.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module '*.svg' { - import { SvgProps } from 'react-native-svg'; - const content: React.FV; - export default content; -} diff --git a/apps/computer-vision/index.ts b/apps/computer-vision/index.ts deleted file mode 100644 index 3f443dcf95..0000000000 --- a/apps/computer-vision/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { registerRootComponent } from 'expo'; - -import App from './app'; - -// registerRootComponent calls AppRegistry.registerComponent('main', () => App); -// It also ensures that whether you load the app in Expo Go or in a native build, -// the environment is set up appropriately -registerRootComponent(App); diff --git a/apps/computer-vision/metro.config.js b/apps/computer-vision/metro.config.js deleted file mode 100644 index f8ab2ab96d..0000000000 --- a/apps/computer-vision/metro.config.js +++ /dev/null @@ -1,21 +0,0 @@ -// Learn more https://docs.expo.io/guides/customizing-metro -const { getDefaultConfig } = require('expo/metro-config'); - -/** @type {import('expo/metro-config').MetroConfig} */ -const config = getDefaultConfig(__dirname); - -const { transformer, resolver } = config; - -config.transformer = { - ...transformer, - babelTransformerPath: require.resolve('react-native-svg-transformer/expo'), -}; -config.resolver = { - ...resolver, - assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'), - sourceExts: [...resolver.sourceExts, 'svg'], -}; - -config.resolver.assetExts.push('pte'); - -module.exports = config; diff --git a/apps/computer-vision/package.json b/apps/computer-vision/package.json deleted file mode 100644 index be06efc27a..0000000000 --- a/apps/computer-vision/package.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "computer-vision", - "version": "1.0.0", - "main": "expo-router/entry", - "scripts": { - "start": "expo start", - "android": "expo run:android", - "ios": "expo run:ios", - "web": "expo start --web", - "typecheck": "tsc", - "lint": "eslint . --ext .ts,.tsx --fix" - }, - "dependencies": { - "@expo/log-box": "~56.0.12", - "@expo/vector-icons": "^15.0.2", - "@react-native/metro-config": "^0.85.3", - "@react-navigation/drawer": "^7.9.4", - "@react-navigation/native": "^7.2.2", - "@shopify/react-native-skia": "2.6.2", - "expo": "~56.0.9", - "expo-build-properties": "~56.0.17", - "expo-constants": "~56.0.17", - "expo-font": "~56.0.5", - "expo-linking": "~56.0.13", - "expo-router": "~56.2.9", - "expo-status-bar": "~56.0.4", - "metro-config": "^0.84.0", - "react": "19.2.3", - "react-native": "0.85.3", - "react-native-device-info": "^15.0.2", - "react-native-executorch": "workspace:*", - "react-native-executorch-expo-resource-fetcher": "workspace:*", - "react-native-gesture-handler": "~2.31.1", - "react-native-image-picker": "^8.2.1", - "react-native-loading-spinner-overlay": "^3.0.1", - "react-native-nitro-image": "0.13.1", - "react-native-nitro-modules": "0.35.4", - "react-native-reanimated": "4.3.1", - "react-native-safe-area-context": "~5.7.0", - "react-native-screens": "~4.25.2", - "react-native-svg": "15.15.4", - "react-native-svg-transformer": "^1.5.3", - "react-native-vision-camera": "^5.0.6", - "react-native-vision-camera-worklets": "^5.0.6", - "react-native-worklets": "0.8.3" - }, - "devDependencies": { - "@babel/core": "^7.29.0", - "@types/react": "~19.2.0", - "@types/react-refresh": "^0", - "babel-preset-expo": "~56.0.14", - "react-refresh": "^0.18.0" - }, - "private": true -} diff --git a/apps/computer-vision/tsconfig.json b/apps/computer-vision/tsconfig.json deleted file mode 100644 index bfe226172e..0000000000 --- a/apps/computer-vision/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "strict": true, - "allowJs": true, - "module": "preserve", - "moduleDetection": "force", - "moduleResolution": "bundler", - "customConditions": ["react-native"], - "noEmit": true, - "paths": { - "react-native-executorch": ["../../packages/react-native-executorch/src"], - "react-native-executorch-expo-resource-fetcher": [ - "../../packages/expo-resource-fetcher/src" - ] - } - } -} diff --git a/apps/computer-vision/utils.ts b/apps/computer-vision/utils.ts deleted file mode 100644 index 3af8d7920e..0000000000 --- a/apps/computer-vision/utils.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - CameraOptions, - launchCamera, - launchImageLibrary, -} from 'react-native-image-picker'; - -export const getImage = async (useCamera: boolean) => { - const options: CameraOptions = { - mediaType: 'photo', - }; - try { - const output = useCamera - ? await launchCamera(options) - : await launchImageLibrary(options); - - if (!output.assets || output.assets.length === 0) return; - - return output.assets[0]; - } catch (err) { - console.error(err); - } -}; diff --git a/apps/llm/.gitignore b/apps/llm/.gitignore deleted file mode 100644 index f779c900a7..0000000000 --- a/apps/llm/.gitignore +++ /dev/null @@ -1,38 +0,0 @@ -# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files - -# dependencies -node_modules/ - -# Expo -.expo/ -dist/ -web-build/ -expo-env.d.ts - -# Native -*.orig.* -*.jks -*.p8 -*.p12 -*.key -*.mobileprovision - -# Metro -.metro-health-check* - -# debug -npm-debug.* -yarn-debug.* -yarn-error.* - -# macOS -.DS_Store -*.pem - -# local env files -.env*.local - -# typescript -*.tsbuildinfo - -.yarn diff --git a/apps/llm/app.json b/apps/llm/app.json deleted file mode 100644 index 43f497d703..0000000000 --- a/apps/llm/app.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "expo": { - "name": "llm", - "slug": "llm", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/icons/icon.png", - "userInterfaceStyle": "light", - "scheme": "rne-llm", - "plugins": [ - [ - "expo-font", - { - "fonts": [ - "./assets/fonts/Aeonik-Regular.otf", - "./assets/fonts/Aeonik-Medium.otf" - ] - } - ], - [ - "expo-calendar", - { - "calendarPermission": "The app needs to access your calendar." - } - ], - "expo-router", - [ - "react-native-audio-api", - { - "iosBackgroundMode": true, - "iosMicrophonePermission": "This app requires access to the microphone to record audio.", - "androidPermissions": [ - "android.permission.MODIFY_AUDIO_SETTINGS", - "android.permission.FOREGROUND_SERVICE", - "android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK", - "android.permission.RECORD_AUDIO" - ], - "androidForegroundService": true, - "androidFSTypes": ["mediaPlayback"] - } - ], - [ - "expo-build-properties", - { - "ios": { - "deploymentTarget": "17.0" - } - } - ] - ], - "newArchEnabled": true, - "splash": { - "image": "./assets/icons/splash.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, - "ios": { - "supportsTablet": true, - "bundleIdentifier": "com.anonymous.llm", - "infoPlist": { - "NSMicrophoneUsageDescription": "This app needs access to your microphone to record audio.", - "NSCalendarsUsageDescription": "This app needs access to your calendar to manage events." - }, - "entitlements": { - "com.apple.developer.kernel.increased-memory-limit": true - } - }, - "android": { - "adaptiveIcon": { - "foregroundImage": "./assets/icons/adaptive-icon.png", - "backgroundColor": "#ffffff" - }, - "package": "com.anonymous.llm", - "permissions": [ - "android.permission.READ_CALENDAR", - "android.permission.WRITE_CALENDAR", - "android.permission.MODIFY_AUDIO_SETTINGS", - "android.permission.FOREGROUND_SERVICE", - "android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK", - "android.permission.RECORD_AUDIO" - ] - }, - "web": { - "favicon": "./assets/icons/favicon.png" - } - } -} diff --git a/apps/llm/app/_layout.tsx b/apps/llm/app/_layout.tsx deleted file mode 100644 index 2cbf4c8c73..0000000000 --- a/apps/llm/app/_layout.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { Drawer } from 'expo-router/drawer'; -import { initExecutorch } from 'react-native-executorch'; -import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher'; -import ColorPalette from '../colors'; -import React, { useState } from 'react'; -import { - Text, - StyleSheet, - View, - TouchableOpacity, - type ColorValue, -} from 'react-native'; -import { - type DrawerContentComponentProps, - DrawerContentScrollView, - DrawerItemList, -} from 'expo-router/build/react-navigation/drawer'; -import { DrawerActions } from 'expo-router/build/react-navigation/routers'; -import { useNavigation } from 'expo-router'; -import Svg, { Rect } from 'react-native-svg'; -import { GeneratingContext } from '../context'; - -function HamburgerIcon({ tintColor }: { tintColor?: ColorValue }) { - const navigation = useNavigation(); - const fill = typeof tintColor === 'string' ? tintColor : '#000'; - return ( - navigation.dispatch(DrawerActions.toggleDrawer())} - style={styles.hamburger} - > - - - - - - - ); -} - -initExecutorch({ - resourceFetcher: ExpoResourceFetcher, -}); - -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 }, - headerLeft: (props) => , - }} - > - null, - title: 'Main Menu', - drawerItemStyle: { display: 'none' }, - }} - /> - - - - - - - - ); -} - -const styles = StyleSheet.create({ - hamburger: { - marginLeft: 12, - }, - 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/index.tsx b/apps/llm/app/index.tsx deleted file mode 100644 index b67b3fa7ce..0000000000 --- a/apps/llm/app/index.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useRouter } from 'expo-router'; -import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; -import ColorPalette from '../colors'; -import ExecutorchLogo from '../assets/icons/executorch.svg'; - -export default function Home() { - const router = useRouter(); - - return ( - - - Select a demo model - - router.navigate('llm/')} - > - LLM - - router.navigate('llm_tool_calling/')} - > - LLM Tool Calling - - router.navigate('llm_structured_output/')} - > - LLM Structured Output - - router.navigate('multimodal_llm/')} - > - Multimodal LLM (VLM) - - router.navigate('privacy_filter/')} - > - Privacy Filter (PII) - - - - ); -} - -export const fontSizes = { - xxl: 34, - xl: 22, - lg: 18, - md: 16, - sm: 14, - xs: 12, - xxs: 10, -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#fff', - }, - headerText: { - fontSize: fontSizes.lg, - color: ColorPalette.strongPrimary, - margin: 20, - }, - buttonContainer: { - width: '80%', - justifyContent: 'space-evenly', - marginBottom: 20, - }, - button: { - backgroundColor: ColorPalette.strongPrimary, - borderRadius: 8, - padding: 10, - alignItems: 'center', - marginBottom: 10, - }, - buttonText: { - color: 'white', - fontSize: fontSizes.md, - }, -}); diff --git a/apps/llm/app/llm/index.tsx b/apps/llm/app/llm/index.tsx deleted file mode 100644 index 8bc0afb623..0000000000 --- a/apps/llm/app/llm/index.tsx +++ /dev/null @@ -1,222 +0,0 @@ -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 { models, useLLM } from 'react-native-executorch'; -import { ModelPicker } from '../../components/ModelPicker'; -import { LLM_MODELS, LLMModelSources } from '../../components/llmModels'; -import PauseIcon from '../../assets/icons/pause_icon.svg'; -import ColorPalette from '../../colors'; -import Messages from '../../components/Messages'; -import { useIsFocused } from 'expo-router'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { GeneratingContext } from '../../context'; -import Spinner from '../../components/Spinner'; -import SuggestedPrompts from '../../components/SuggestedPrompts'; - -const SUGGESTED_PROMPTS = [ - 'Explain quantum computing in simple terms', - 'Write a short poem about coding', - 'What are the benefits of on-device AI?', - 'Give me 3 fun facts about space', -]; -import { useLLMStats } from '../../hooks/useLLMStats'; -import { StatsBar } from '../../components/StatsBar'; -import ErrorBanner from '../../components/ErrorBanner'; - -export default function LLMScreenWrapper() { - const isFocused = useIsFocused(); - return isFocused ? : null; -} - -function LLMScreen() { - const { bottom } = useSafeAreaInsets(); - const [isTextInputFocused, setIsTextInputFocused] = useState(false); - const [userInput, setUserInput] = useState(''); - const [selectedModel, setSelectedModel] = useState( - models.llm.lfm2_5_1_2b_instruct() - ); - const textInputRef = useRef(null); - const { setGlobalGenerating } = useContext(GeneratingContext); - - const llm = useLLM({ model: selectedModel }); - const tokenCount = llm.isReady ? llm.getGeneratedTokenCount() : 0; - const { stats, onMessageSend } = useLLMStats( - llm.response, - llm.isGenerating, - tokenCount - ); - const [error, setError] = useState(null); - - useEffect(() => { - if (llm.error) setError(String(llm.error)); - }, [llm.error]); - - useEffect(() => { - setGlobalGenerating(llm.isGenerating); - }, [llm.isGenerating, setGlobalGenerating]); - - const sendMessage = async () => { - onMessageSend(); - setUserInput(''); - textInputRef.current?.clear(); - try { - await llm.sendMessage(userInput); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - }; - - return !llm.isReady && !llm.error ? ( - - ) : ( - - - - setError(null)} /> - {llm.messageHistory.length ? ( - - - - ) : ( - - Hello! 👋 - - What can I help you with? - - - - )} - - setSelectedModel(m)} - disabled={llm.isGenerating} - /> - - - setIsTextInputFocused(true)} - onBlur={() => setIsTextInputFocused(false)} - style={{ - ...styles.textInput, - borderColor: isTextInputFocused - ? ColorPalette.blueDark - : ColorPalette.blueLight, - }} - placeholder={isTextInputFocused ? '' : 'Your message'} - placeholderTextColor={'#C1C6E5'} - multiline={true} - ref={textInputRef} - value={userInput} - onChangeText={(text: string) => setUserInput(text)} - /> - {userInput && ( - !llm.isGenerating && (await sendMessage())} - > - - - )} - {llm.isGenerating && ( - - - - )} - - - - - ); -} - -const styles = StyleSheet.create({ - keyboardAvoidingView: { - flex: 1, - paddingBottom: Platform.OS === 'android' ? 20 : 0, - }, - container: { flex: 1 }, - chatContainer: { flex: 10, width: '100%' }, - helloMessageContainer: { - flex: 10, - width: '100%', - alignItems: 'center', - justifyContent: 'center', - overflow: 'hidden', - }, - helloText: { - fontFamily: 'medium', - fontSize: 30, - color: ColorPalette.primary, - }, - bottomHelloText: { - fontFamily: 'regular', - fontSize: 20, - lineHeight: 28, - textAlign: 'center', - 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_structured_output/index.tsx b/apps/llm/app/llm_structured_output/index.tsx deleted file mode 100644 index fc1474fa9c..0000000000 --- a/apps/llm/app/llm_structured_output/index.tsx +++ /dev/null @@ -1,297 +0,0 @@ -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 '../../components/Spinner'; -import SuggestedPrompts from '../../components/SuggestedPrompts'; - -const SUGGESTED_PROMPTS = [ - "I'm John. Is this damaged? I offer $100.", - "Hi, I'm Alice! How old is it? Offering $250 USD.", - "I'm Bob. Does it have warranty? I'll pay €50.", - "Name's Sara. What condition? My bid is $75.", -]; -import { useLLMStats } from '../../hooks/useLLMStats'; -import { StatsBar } from '../../components/StatsBar'; -import ErrorBanner from '../../components/ErrorBanner'; -import { - models, - useLLM, - fixAndValidateStructuredOutput, - getStructuredOutputPrompt, -} from 'react-native-executorch'; -import { ModelPicker } from '../../components/ModelPicker'; -import { LLM_MODELS, LLMModelSources } from '../../components/llmModels'; -import PauseIcon from '../../assets/icons/pause_icon.svg'; -import ColorPalette from '../../colors'; -import Messages from '../../components/Messages'; -import { useIsFocused } from 'expo-router'; -import { GeneratingContext } from '../../context'; -import { Schema } from 'jsonschema'; -import * as z from 'zod/v4'; - -// Defining schemas -const responseSchema: Schema = { - properties: { - username: { - type: 'string', - description: 'Name of user, that is asking a question.', - }, - question: { - type: 'string', - description: 'Question that user asks.', - }, - bid: { - type: 'number', - description: 'Amount of money, that user offers.', - }, - currency: { - type: 'string', - description: 'Currency of offer.', - }, - }, - required: ['username', 'bid'], - type: 'object', -}; - -const responseSchemaWithZod = z.object({ - username: z - .string() - .meta({ description: 'Name of user, that is asking a question.' }), - question: z.optional( - z.string().meta({ description: 'Question that user asks.' }) - ), - bid: z.number().meta({ description: 'Amount of money, that user offers.' }), - currency: z.optional(z.string().meta({ description: 'Currency of offer.' })), -}); - -export default function LLMScreenWrapper() { - const isFocused = useIsFocused(); - - return isFocused ? : null; -} - -function LLMScreen() { - const [isTextInputFocused, setIsTextInputFocused] = useState(false); - const [userInput, setUserInput] = useState(''); - const [selectedModel, setSelectedModel] = useState( - models.llm.qwen3_1_7b() - ); - const textInputRef = useRef(null); - const { setGlobalGenerating } = useContext(GeneratingContext); - - const llm = useLLM({ model: selectedModel }); - const tokenCount = llm.isReady ? llm.getGeneratedTokenCount() : 0; - const { stats, onMessageSend } = useLLMStats( - llm.response, - llm.isGenerating, - tokenCount - ); - const [error, setError] = useState(null); - - useEffect(() => { - setGlobalGenerating(llm.isGenerating); - }, [llm.isGenerating, setGlobalGenerating]); - - const { configure } = llm; - useEffect(() => { - const formattingInstructions = getStructuredOutputPrompt(responseSchema); - const prompt = `Your goal is to parse user's messages and return them in JSON format. Don't respond to user. Simply return JSON with user's question parsed. \n${formattingInstructions}\n /no_think`; - - configure({ - chatConfig: { - systemPrompt: prompt, - }, - }); - }, [configure]); - - useEffect(() => { - const lastMessage = llm.messageHistory.at(-1); - if (!llm.isGenerating && lastMessage?.role === 'assistant') { - try { - const formattedOutput = fixAndValidateStructuredOutput( - lastMessage.content, - responseSchema - ); - const formattedOutputWithZod = fixAndValidateStructuredOutput( - lastMessage.content, - responseSchemaWithZod - ); - console.log( - 'Formatted output:', - formattedOutput, - formattedOutputWithZod - ); - } catch (e) { - console.log( - "Error parsing output and/or output doesn't match required schema!", - e - ); - } - } - }, [llm.messageHistory, llm.isGenerating]); - - useEffect(() => { - if (llm.error) setError(String(llm.error)); - }, [llm.error]); - - const sendMessage = async () => { - onMessageSend(); - setUserInput(''); - textInputRef.current?.clear(); - try { - await llm.sendMessage(userInput); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - }; - - return !llm.isReady && !llm.error ? ( - - ) : ( - - - - setError(null)} /> - {llm.messageHistory.length ? ( - - - - ) : ( - - Hello! 👋 - - I can parse user's questions! Introduce yourself, ask questions - and offer a price for some product. - - - - )} - - setSelectedModel(m)} - disabled={llm.isGenerating} - /> - - - setIsTextInputFocused(true)} - onBlur={() => setIsTextInputFocused(false)} - style={{ - ...styles.textInput, - borderColor: isTextInputFocused - ? ColorPalette.blueDark - : ColorPalette.blueLight, - }} - placeholder={ - isTextInputFocused - ? '' - : "Your message e.g. I'm John. Is this product damaged? I can give you $100 for this." - } - placeholderTextColor={'#C1C6E5'} - multiline={true} - ref={textInputRef} - value={userInput} - onChangeText={(text: string) => setUserInput(text)} - /> - {userInput && ( - !llm.isGenerating && (await sendMessage())} - > - - - )} - {llm.isGenerating && ( - - - - )} - - - - - ); -} - -const styles = StyleSheet.create({ - keyboardAvoidingView: { flex: 1 }, - container: { flex: 1, paddingBottom: Platform.OS === 'android' ? 20 : 0 }, - chatContainer: { flex: 10, width: '100%' }, - helloMessageContainer: { - flex: 10, - width: '100%', - alignItems: 'center', - justifyContent: 'center', - }, - helloText: { - fontFamily: 'medium', - fontSize: 30, - color: ColorPalette.primary, - }, - bottomHelloText: { - padding: 20, - fontFamily: 'regular', - fontSize: 20, - lineHeight: 28, - textAlign: 'center', - 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 deleted file mode 100644 index 7ca42d2adc..0000000000 --- a/apps/llm/app/llm_tool_calling/index.tsx +++ /dev/null @@ -1,354 +0,0 @@ -import { useContext, useEffect, useRef, useState } from 'react'; -import { - Keyboard, - KeyboardAvoidingView, - Platform, - StyleSheet, - Text, - TextInput, - TouchableOpacity, - TouchableWithoutFeedback, - View, - Linking, - Alert, - AppState, -} from 'react-native'; -import SWMIcon from '../../assets/icons/swm_icon.svg'; -import SendIcon from '../../assets/icons/send_icon.svg'; -import Spinner from '../../components/Spinner'; -import ErrorBanner from '../../components/ErrorBanner'; -import { models, useLLM, DEFAULT_SYSTEM_PROMPT } from 'react-native-executorch'; -import { ModelPicker } from '../../components/ModelPicker'; -import { LLM_MODELS, LLMModelSources } from '../../components/llmModels'; -import PauseIcon from '../../assets/icons/pause_icon.svg'; -import ColorPalette from '../../colors'; -import Messages from '../../components/Messages'; -import * as Brightness from 'expo-brightness'; -import * as Calendar from 'expo-calendar'; -import { PermissionStatus } from 'expo'; -import { executeTool, TOOL_DEFINITIONS_PHONE } from '../../utils/tools'; -import { useIsFocused } from 'expo-router'; -import { GeneratingContext } from '../../context'; -import SuggestedPrompts from '../../components/SuggestedPrompts'; - -const SUGGESTED_PROMPTS = [ - 'What events do I have today?', - 'Add a meeting tomorrow at 2pm', - 'Set screen brightness to 50%', - 'What do I have scheduled this week?', -]; -import { useLLMStats } from '../../hooks/useLLMStats'; -import { StatsBar } from '../../components/StatsBar'; - -export default function LLMToolCallingScreenWrapper() { - const isFocused = useIsFocused(); - - return isFocused ? : null; -} - -function LLMToolCallingScreen() { - const [isTextInputFocused, setIsTextInputFocused] = useState(false); - const [userInput, setUserInput] = useState(''); - const [hasCalendarPermission, setHasCalendarPermission] = useState(true); - const [hasBrightnessPermission, setHasBrightnessPermission] = useState(true); - const [selectedModel, setSelectedModel] = useState( - models.llm.hammer2_1_1_5b() - ); - const textInputRef = useRef(null); - const { setGlobalGenerating } = useContext(GeneratingContext); - - const llm = useLLM({ model: selectedModel }); - const tokenCount = llm.isReady ? llm.getGeneratedTokenCount() : 0; - const { stats, onMessageSend } = useLLMStats( - llm.response, - llm.isGenerating, - tokenCount - ); - const [error, setError] = useState(null); - - useEffect(() => { - setGlobalGenerating(llm.isGenerating); - }, [llm.isGenerating, setGlobalGenerating]); - - const { configure } = llm; - useEffect(() => { - configure({ - chatConfig: { - systemPrompt: `${DEFAULT_SYSTEM_PROMPT} Current time and date: ${new Date().toString()}`, - }, - toolsConfig: { - tools: TOOL_DEFINITIONS_PHONE, - executeToolCallback: executeTool, - displayToolCalls: true, - }, - }); - }, [configure]); - - useEffect(() => { - if (llm.error) setError(String(llm.error)); - }, [llm.error]); - - const requestCalendarPermission = async () => { - const { status, canAskAgain } = - await Calendar.getCalendarPermissionsAsync(); - - if (status === PermissionStatus.GRANTED) { - setHasCalendarPermission(true); - return; - } - - if (status === PermissionStatus.UNDETERMINED || canAskAgain) { - const { status: nextStatus } = - await Calendar.requestCalendarPermissionsAsync(); - setHasCalendarPermission(nextStatus === PermissionStatus.GRANTED); - return; - } - - setHasCalendarPermission(false); - Alert.alert( - 'Calendar Permission Required', - 'To read or add events, the app requires "Full Access" to your calendar. Please enable this in your device settings.', - [ - { text: 'Cancel', style: 'cancel' }, - { text: 'Open Settings', onPress: () => Linking.openSettings() }, - ] - ); - }; - - const requestBrightnessPermission = async () => { - const { status, canAskAgain } = await Brightness.getPermissionsAsync(); - - if (status === Brightness.PermissionStatus.GRANTED) { - setHasBrightnessPermission(true); - return; - } - - if (status === Brightness.PermissionStatus.UNDETERMINED || canAskAgain) { - const { status: nextStatus } = await Brightness.requestPermissionsAsync(); - setHasBrightnessPermission( - nextStatus === Brightness.PermissionStatus.GRANTED - ); - return; - } - - setHasBrightnessPermission(false); - Alert.alert( - 'Brightness Permission Required', - 'To change screen brightness, the app requires permission. Please enable this in your device settings.', - [ - { text: 'Cancel', style: 'cancel' }, - { text: 'Open Settings', onPress: () => Linking.openSettings() }, - ] - ); - }; - - useEffect(() => { - requestCalendarPermission(); - requestBrightnessPermission(); - }, []); - - useEffect(() => { - const subscription = AppState.addEventListener('change', (nextAppState) => { - if (nextAppState === 'active') { - requestCalendarPermission(); - requestBrightnessPermission(); - } - }); - - return () => { - subscription.remove(); - }; - }, []); - - const sendMessage = async () => { - onMessageSend(); - setUserInput(''); - textInputRef.current?.clear(); - try { - await llm.sendMessage(userInput); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - }; - - return !llm.isReady && !llm.error ? ( - - ) : ( - - - - - - - setError(null)} /> - {llm.messageHistory.length ? ( - - - - ) : ( - - Hello! 👋 - - I can use calendar! Ask me to check it or add an event for you! - - - - )} - - {!hasCalendarPermission && ( - - - Calendar access is required.{' '} - Grant Access - - - )} - {!hasBrightnessPermission && ( - - - Brightness access is required.{' '} - Grant Access - - - )} - - setSelectedModel(m)} - disabled={llm.isGenerating} - /> - - - setIsTextInputFocused(true)} - onBlur={() => setIsTextInputFocused(false)} - style={{ - ...styles.textInput, - borderColor: isTextInputFocused - ? ColorPalette.blueDark - : ColorPalette.blueLight, - }} - placeholder={isTextInputFocused ? '' : 'Your message'} - placeholderTextColor={'#C1C6E5'} - multiline={true} - ref={textInputRef} - value={userInput} - onChangeText={(text: string) => setUserInput(text)} - /> - {userInput && ( - !llm.isGenerating && (await sendMessage())} - > - - - )} - {llm.isGenerating && ( - - - - )} - - - - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, paddingBottom: Platform.OS === 'android' ? 20 : 0 }, - keyboardAvoidingView: { flex: 1 }, - topContainer: { - height: 68, - width: '100%', - alignItems: 'center', - justifyContent: 'center', - }, - chatContainer: { flex: 10, width: '100%' }, - textModelName: { color: ColorPalette.primary }, - helloMessageContainer: { - flex: 10, - width: '100%', - alignItems: 'center', - justifyContent: 'center', - }, - helloText: { - fontFamily: 'medium', - fontSize: 30, - color: ColorPalette.primary, - }, - bottomHelloText: { - padding: 20, - fontFamily: 'regular', - fontSize: 20, - lineHeight: 28, - textAlign: 'center', - 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', - }, - permissionBanner: { - backgroundColor: '#FFF3CD', - paddingVertical: 10, - paddingHorizontal: 16, - alignItems: 'center', - }, - permissionBannerText: { - fontFamily: 'regular', - fontSize: 13, - color: '#856404', - }, - permissionBannerLink: { - fontFamily: 'medium', - color: ColorPalette.blueDark, - }, -}); diff --git a/apps/llm/app/multimodal_llm/index.tsx b/apps/llm/app/multimodal_llm/index.tsx deleted file mode 100644 index a7e5ef712a..0000000000 --- a/apps/llm/app/multimodal_llm/index.tsx +++ /dev/null @@ -1,585 +0,0 @@ -import { useContext, useEffect, useRef, useState } from 'react'; -import { - Image, - Keyboard, - KeyboardAvoidingView, - Platform, - StyleSheet, - Text, - TextInput, - TouchableOpacity, - TouchableWithoutFeedback, - View, -} from 'react-native'; -import { launchImageLibrary } from 'react-native-image-picker'; -import { - AudioManager, - AudioRecorder, - AudioContext, -} from 'react-native-audio-api'; -import { useIsFocused } from 'expo-router'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { models, useLLM } from 'react-native-executorch'; -import SendIcon from '../../assets/icons/send_icon.svg'; -import PauseIcon from '../../assets/icons/pause_icon.svg'; -import ColorPalette from '../../colors'; -import Messages from '../../components/Messages'; -import Spinner from '../../components/Spinner'; -import { GeneratingContext } from '../../context'; -import SuggestedPrompts from '../../components/SuggestedPrompts'; -import ErrorBanner from '../../components/ErrorBanner'; -import AudioWaveform from '../../components/AudioWaveform'; - -const SUGGESTED_PROMPTS = [ - "What's in this image?", - 'Describe this scene in detail', - 'What objects can you see?', - 'What text appears in this image?', - 'Transcribe the audio', -]; -import { useLLMStats } from '../../hooks/useLLMStats'; -import { StatsBar } from '../../components/StatsBar'; - -export default function MultimodalLLMScreenWrapper() { - const isFocused = useIsFocused(); - return isFocused ? : null; -} - -function MultimodalLLMScreen() { - const { bottom } = useSafeAreaInsets(); - const [imageUri, setImageUri] = useState(null); - const [userInput, setUserInput] = useState(''); - const [isTextInputFocused, setIsTextInputFocused] = useState(false); - const textInputRef = useRef(null); - const { setGlobalGenerating } = useContext(GeneratingContext); - - const [audioBuffer, setAudioBuffer] = useState(null); - const [audioLabel, setAudioLabel] = useState(null); - const [audioUrl, setAudioUrl] = useState(''); - const [isFetchingAudio, setIsFetchingAudio] = useState(false); - const [isRecording, setIsRecording] = useState(false); - const [hasMicPermission, setHasMicPermission] = useState(false); - const recorder = useRef(new AudioRecorder()); - const recordChunks = useRef([]); - - const [error, setError] = useState(null); - const model = models.llm.gemma4_e2b_multimodal(); - const vlm = useLLM({ model: model }); - const tokenCount = vlm.isReady ? vlm.getGeneratedTokenCount() : 0; - const { stats, onMessageSend } = useLLMStats( - vlm.response, - vlm.isGenerating, - tokenCount - ); - - useEffect(() => { - setGlobalGenerating(vlm.isGenerating); - }, [vlm.isGenerating, setGlobalGenerating]); - - // Updated to use local error state - useEffect(() => { - if (vlm.error) setError(String(vlm.error)); - }, [vlm.error]); - - useEffect(() => { - AudioManager.setAudioSessionOptions({ - iosCategory: 'playAndRecord', - iosMode: 'spokenAudio', - iosOptions: ['allowBluetoothHFP', 'defaultToSpeaker'], - }); - (async () => { - const status = await AudioManager.requestRecordingPermissions(); - setHasMicPermission(status === 'Granted'); - })(); - - return () => { - if (vlm.isGenerating) vlm.interrupt(); - // eslint-disable-next-line react-hooks/exhaustive-deps - recorder.current.stop(); - AudioManager.setAudioSessionActivity(false); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const loadAudioFromUrl = async () => { - const url = audioUrl.trim(); - if (!url) return; - setIsFetchingAudio(true); - try { - const ctx = new AudioContext({ sampleRate: 16000 }); - const decoded = await ctx.decodeAudioData(url); - const pcm = decoded.getChannelData(0); - const name = url.split('/').pop() || 'audio'; - setAudioBuffer(pcm); - setAudioLabel(`${name} · ${(pcm.length / 16000).toFixed(1)}s`); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } finally { - setIsFetchingAudio(false); - } - }; - - const startRecording = async () => { - if (!hasMicPermission) { - setError('Microphone permission denied. Please enable it in Settings.'); - return; - } - recordChunks.current = []; - const sampleRate = 16000; - recorder.current.onAudioReady( - { sampleRate, bufferLength: 0.1 * sampleRate, channelCount: 1 }, - ({ buffer }) => { - recordChunks.current.push(new Float32Array(buffer.getChannelData(0))); - } - ); - try { - const ok = await AudioManager.setAudioSessionActivity(true); - if (!ok) { - setError('Cannot start audio session'); - return; - } - const result = recorder.current.start(); - if (result.status === 'error') { - setError(`Recording problems: ${result.message}`); - return; - } - setIsRecording(true); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - }; - - const stopRecording = () => { - recorder.current.stop(); - setIsRecording(false); - const total = recordChunks.current.reduce((n, c) => n + c.length, 0); - if (total === 0) return; - const pcm = new Float32Array(total); - let off = 0; - for (const c of recordChunks.current) { - pcm.set(c, off); - off += c.length; - } - recordChunks.current = []; - setAudioBuffer(pcm); - setAudioLabel(`Recording · ${(pcm.length / 16000).toFixed(1)}s`); - }; - - const clearAudio = () => { - setAudioBuffer(null); - setAudioLabel(null); - }; - - const pickImage = async () => { - try { - const result = await launchImageLibrary({ mediaType: 'photo' }); - if (result.assets && result.assets.length > 0) { - const uri = result.assets[0]?.uri; - if (uri) setImageUri(uri); - } - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - }; - - const sendMessage = async () => { - if (!(imageUri || audioBuffer || userInput.trim()) || vlm.isGenerating) - return; - onMessageSend(); - const text = userInput.trim(); - setUserInput(''); - textInputRef.current?.clear(); - Keyboard.dismiss(); - const currentImageUri = imageUri; - const currentAudio = audioBuffer; - setImageUri(null); - setAudioBuffer(null); - setAudioLabel(null); - try { - const media = - currentImageUri || currentAudio - ? { - ...(currentImageUri ? { imagePath: currentImageUri } : {}), - ...(currentAudio ? { audioBuffer: currentAudio } : {}), - } - : undefined; - await vlm.sendMessage(text, media); - } catch (e) { - // Updated to set UI error instead of just console.error - setError(e instanceof Error ? e.message : String(e)); - } - }; - - // Updated Spinner check so it doesn't block the ErrorBanner if loading fails - if (!vlm.isReady && !vlm.error) { - return ( - - ); - } - - return ( - - - - {/* Injected ErrorBanner here */} - setError(null)} /> - - {vlm.messageHistory.length ? ( - - - - ) : ( - - Hello! 👋 - - {model.capabilities.find((c) => c === 'audio') - ? 'Say hi, or pick an image, and ask me anything about it.' - : 'Pick an image and ask me anything about it.'} - - - - )} - - {/* Image thumbnail strip */} - {imageUri && ( - - - Tap to change - - )} - - {/* Audio URL input */} - - - - - {isFetchingAudio ? '…' : 'Load'} - - - - - {/* Audio attachment strip */} - {audioLabel && ( - - - 🎵 {audioLabel} - - - - - - - )} - - - - {/* Image picker button */} - - 📷 - - - {/* Mic record / stop button */} - - - {isRecording ? '⏹️' : '🎤'} - - - - setIsTextInputFocused(true)} - onBlur={() => setIsTextInputFocused(false)} - style={[ - styles.textInput, - { - borderColor: isTextInputFocused - ? ColorPalette.blueDark - : ColorPalette.blueLight, - }, - ]} - placeholder={imageUri ? 'Ask about the image…' : 'Your message'} - placeholderTextColor="#C1C6E5" - multiline - value={userInput} - onChangeText={setUserInput} - /> - - {(imageUri || audioBuffer || userInput.trim()) && - !vlm.isGenerating && ( - - - - )} - {vlm.isGenerating && ( - - - - )} - - - - - ); -} - -const styles = StyleSheet.create({ - // Setup phase - setupContainer: { - flex: 1, - padding: 24, - backgroundColor: '#fff', - justifyContent: 'center', - }, - setupTitle: { - fontSize: 20, - fontFamily: 'medium', - color: ColorPalette.primary, - marginBottom: 8, - }, - setupHint: { - fontSize: 13, - fontFamily: 'regular', - color: ColorPalette.blueDark, - marginBottom: 32, - lineHeight: 18, - }, - filePickerRow: { - flexDirection: 'row', - alignItems: 'center', - borderWidth: 1, - borderColor: ColorPalette.blueLight, - borderRadius: 10, - padding: 14, - marginBottom: 12, - backgroundColor: '#fafbff', - }, - filePickerInfo: { flex: 1 }, - filePickerLabel: { - fontSize: 12, - fontFamily: 'medium', - color: ColorPalette.blueDark, - marginBottom: 2, - }, - filePickerValue: { fontSize: 14, fontFamily: 'regular' }, - filePickerValueSet: { color: ColorPalette.primary }, - filePickerValueEmpty: { color: ColorPalette.blueLight }, - filePickerChevron: { - fontSize: 24, - color: ColorPalette.blueLight, - marginLeft: 8, - }, - loadButton: { - marginTop: 16, - backgroundColor: ColorPalette.strongPrimary, - borderRadius: 10, - padding: 14, - alignItems: 'center', - }, - loadButtonDisabled: { backgroundColor: ColorPalette.blueLight }, - loadButtonText: { color: '#fff', fontFamily: 'medium', fontSize: 15 }, - - // Chat phase - container: { flex: 1 }, - chatContainer: { flex: 10, width: '100%' }, - helloMessageContainer: { - flex: 10, - width: '100%', - alignItems: 'center', - justifyContent: 'center', - overflow: 'hidden', - }, - helloText: { - fontFamily: 'medium', - fontSize: 30, - color: ColorPalette.primary, - }, - bottomHelloText: { - fontFamily: 'regular', - fontSize: 20, - lineHeight: 28, - textAlign: 'center', - color: ColorPalette.primary, - paddingHorizontal: 24, - }, - imageThumbnailContainer: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 16, - paddingVertical: 6, - gap: 8, - }, - imageThumbnail: { - width: 48, - height: 48, - borderRadius: 8, - borderWidth: 1, - borderColor: ColorPalette.blueLight, - }, - imageThumbnailHint: { - fontSize: 12, - fontFamily: 'regular', - color: ColorPalette.blueDark, - }, - audioAttachmentContainer: { - flexDirection: 'column', - paddingHorizontal: 16, - paddingVertical: 8, - marginHorizontal: 16, - marginBottom: 4, - borderRadius: 8, - borderWidth: 1, - borderColor: ColorPalette.blueLight, - backgroundColor: '#fafbff', - }, - audioAttachmentRow: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, - audioAttachmentText: { - fontSize: 13, - fontFamily: 'regular', - color: ColorPalette.blueDark, - }, - audioAttachmentClear: { - fontSize: 16, - color: ColorPalette.blueDark, - paddingHorizontal: 8, - }, - audioWaveform: { - marginTop: 6, - minWidth: 0, - }, - audioUrlRow: { - flexDirection: 'row', - alignItems: 'center', - marginHorizontal: 16, - marginBottom: 4, - }, - audioUrlInput: { - flex: 1, - padding: 10, - borderTopLeftRadius: 8, - borderBottomLeftRadius: 8, - borderWidth: 1, - borderColor: ColorPalette.blueLight, - borderRightWidth: 0, - fontFamily: 'regular', - fontSize: 13, - color: ColorPalette.primary, - }, - audioUrlButton: { - paddingVertical: 10, - paddingHorizontal: 16, - backgroundColor: ColorPalette.strongPrimary, - borderTopRightRadius: 8, - borderBottomRightRadius: 8, - justifyContent: 'center', - alignItems: 'center', - }, - audioUrlButtonText: { - color: '#fff', - fontFamily: 'medium', - fontSize: 13, - }, - disabled: { - opacity: 0.5, - }, - bottomContainer: { - height: 100, - width: '100%', - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingHorizontal: 16, - }, - imageButton: { - width: 40, - height: 40, - justifyContent: 'center', - alignItems: 'center', - marginRight: 4, - }, - imageButtonText: { fontSize: 22 }, - 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/privacy_filter/index.tsx b/apps/llm/app/privacy_filter/index.tsx deleted file mode 100644 index 05870921a1..0000000000 --- a/apps/llm/app/privacy_filter/index.tsx +++ /dev/null @@ -1,255 +0,0 @@ -import { useMemo, useState } from 'react'; -import { - ActivityIndicator, - ScrollView, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; -import { useIsFocused } from 'expo-router'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { - models, - PiiEntity, - PrivacyFilterModelSources, - usePrivacyFilter, -} from 'react-native-executorch'; - -const privacyFilter = models.privacy_filter; -import ColorPalette from '../../colors'; -import { ModelOption, ModelPicker } from '../../components/ModelPicker'; -import { - buildSegments, - colorForLabel, - matchEntities, -} from '../../utils/piiMatching'; - -/* cspell:disable */ -// Sample tuned for the OpenAI base model — exercises the 8 entity types it -// recognizes (person, email, phone, account_number, address, date, url, -// secret). -const OPENAI_SAMPLE = `My name is Sarah Chen and I work as a senior engineer at Acme Corp. You can reach me at sarah.chen@acmecorp.io or call my direct line at (415) 923-0847. For billing inquiries, my account number is ACC-8821-4490-3371. - -I've been living at 17 Birchwood Lane, Portland, OR 97201 since October 3rd, 2019. Before that I was at 8 Rue de Rivoli, Paris, 75001, France. My personal website is https://sarahchen.dev and my GitHub is https://github.com/schen-eng. Feel free to connect — I usually respond within a business day. - -My date of birth is June 12, 1991, and my backup email is s.chen.personal@gmail.com in case the primary address is unreachable. This message also contains a confidential API key: sk-T93kXpLm2NvBqR7dYwZ4. Please do not share it outside the team. You can also reach my colleague James Okonkwo at j.okonkwo@acmecorp.io or at his mobile +44 7911 123456.`; -// Sample tuned for the OpenMed Nemotron model — covers categories the base -// OpenAI model doesn't have (medical, financial, technical, demographic). - -const NEMOTRON_SAMPLE = `Patient intake for Maria Lopez, female, age 47, blood type O+, born 1978-05-12. MRN 994-2210-AB; health plan beneficiary number HPBN-552-9931 with Aetna. SSN 412-55-7821, national ID DNI 88-7762-X. Primary occupation: registered nurse, currently employed full-time at Mercy General. Religion: Catholic; political view: independent. - -Reach her at maria.lopez@example.com or +1 (415) 555-0142. Mailing address: 84 Cedar Hill Road, Apt 3B, Berkeley, CA 94703, United States. Vehicle plate 7XKL922; driver license CA-D1294883. - -Payment for last visit: Visa ending 4992-1133-7820-4419, expires 11/28, CVV 884. Bank routing 021000089, SWIFT BIC CHASUS33. Employer EIN tax ID 47-3320118. Customer ID CUST-553201, employee ID EMP-A0093. - -Workstation MAC 3C:22:FB:8E:01:9A, IPv4 10.0.42.118, device IMEI 359888061234560. Service account API key sk-live-Tn8x3pLm2NvBqR7dYwZ4QF, password Hunter2!Spring. Session cookie sid=eyJ1c2VyIjoiOTk0MjIxMCJ9.`; -/* cspell:enable */ - -const MODEL_OPTIONS: ModelOption[] = [ - { - label: 'OpenAI Privacy Filter (8 entities)', - value: privacyFilter.openai(), - }, - { - label: 'OpenMed Nemotron (55 entities)', - value: privacyFilter.nemotron(), - }, -]; - -// Pick the right sample to display/run based on the active model. -function sampleFor(model: PrivacyFilterModelSources): string { - return model.modelName === privacyFilter.nemotron().modelName - ? NEMOTRON_SAMPLE - : OPENAI_SAMPLE; -} - -function HighlightedText({ - source, - entities, -}: { - source: string; - entities: PiiEntity[]; -}) { - const segments = useMemo( - () => buildSegments(source, matchEntities(source, entities)), - [source, entities] - ); - return ( - - {segments.map((seg, i) => - seg.label ? ( - - {seg.text} - - ) : ( - {seg.text} - ) - )} - - ); -} - -function PrivacyFilterScreen() { - const { bottom } = useSafeAreaInsets(); - const [entities, setEntities] = useState(null); - const [runError, setRunError] = useState(null); - const [inferenceMs, setInferenceMs] = useState(null); - const [selectedModel, setSelectedModel] = useState( - privacyFilter.openai() - ); - - const filter = usePrivacyFilter({ model: selectedModel }); - const sampleText = sampleFor(selectedModel); - - const onRun = async () => { - setRunError(null); - setEntities(null); - setInferenceMs(null); - const startedAt = Date.now(); - try { - const result = await filter.generate(sampleText); - const elapsed = Date.now() - startedAt; - setInferenceMs(elapsed); - setEntities(result); - } catch (e) { - setRunError(e instanceof Error ? e.message : String(e)); - } - }; - - const disabled = !filter.isReady || filter.isGenerating; - - return ( - - { - setEntities(null); - setRunError(null); - setInferenceMs(null); - setSelectedModel(m); - }} - label="Model" - disabled={filter.isGenerating} - /> - - {filter.error && ( - - - Load error: {filter.error.message} - - - )} - - {!filter.isReady && !filter.error && ( - - - - Downloading model…{' '} - {Math.round((filter.downloadProgress ?? 0) * 100)}% - - - )} - - - {entities ? ( - - ) : ( - {sampleText} - )} - - - - {filter.isGenerating ? ( - - ) : ( - - Detect PII - {inferenceMs !== null && ` · ${inferenceMs} ms`} - - )} - - - {runError && ( - - Run error: {runError} - - )} - - ); -} - -export default function PrivacyFilterScreenWrapper() { - const isFocused = useIsFocused(); - return isFocused ? : null; -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - padding: 16, - backgroundColor: '#fff', - gap: 10, - }, - textBox: { - flex: 1, - borderWidth: 1, - borderColor: '#e0e0e0', - borderRadius: 8, - padding: 10, - }, - sampleText: { - fontSize: 13, - color: '#222', - lineHeight: 19, - }, - highlight: { - fontWeight: '600', - borderRadius: 3, - }, - runButton: { - backgroundColor: ColorPalette.primary, - borderRadius: 8, - paddingVertical: 12, - alignItems: 'center', - }, - runButtonText: { - color: '#fff', - fontSize: 15, - fontWeight: '600', - }, - buttonDisabled: { - opacity: 0.5, - }, - centerBlock: { - alignItems: 'center', - gap: 6, - paddingVertical: 8, - }, - muted: { - color: '#666', - fontSize: 12, - }, - errorBanner: { - backgroundColor: '#fdecea', - borderColor: '#f5c6cb', - borderWidth: 1, - borderRadius: 6, - padding: 8, - }, - errorText: { - color: '#a94442', - fontSize: 12, - }, -}); diff --git a/apps/llm/assets/fonts/Aeonik-Medium.otf b/apps/llm/assets/fonts/Aeonik-Medium.otf deleted file mode 100644 index cd9981f439..0000000000 Binary files a/apps/llm/assets/fonts/Aeonik-Medium.otf and /dev/null differ diff --git a/apps/llm/assets/fonts/Aeonik-Regular.otf b/apps/llm/assets/fonts/Aeonik-Regular.otf deleted file mode 100644 index 9bd378ed2b..0000000000 Binary files a/apps/llm/assets/fonts/Aeonik-Regular.otf and /dev/null differ diff --git a/apps/llm/assets/icons/adaptive-icon.png b/apps/llm/assets/icons/adaptive-icon.png deleted file mode 100644 index 03d6f6b6c6..0000000000 Binary files a/apps/llm/assets/icons/adaptive-icon.png and /dev/null differ diff --git a/apps/llm/assets/icons/executorch.svg b/apps/llm/assets/icons/executorch.svg deleted file mode 100644 index e548ea4201..0000000000 --- a/apps/llm/assets/icons/executorch.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/apps/llm/assets/icons/favicon.png b/apps/llm/assets/icons/favicon.png deleted file mode 100644 index e75f697b18..0000000000 Binary files a/apps/llm/assets/icons/favicon.png and /dev/null differ diff --git a/apps/llm/assets/icons/icon.png b/apps/llm/assets/icons/icon.png deleted file mode 100644 index a0b1526fc7..0000000000 Binary files a/apps/llm/assets/icons/icon.png and /dev/null differ diff --git a/apps/llm/assets/icons/llama_icon.svg b/apps/llm/assets/icons/llama_icon.svg deleted file mode 100644 index 99e29696ec..0000000000 --- a/apps/llm/assets/icons/llama_icon.svg +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/apps/llm/assets/icons/mic_icon.svg b/apps/llm/assets/icons/mic_icon.svg deleted file mode 100644 index 955808bfba..0000000000 --- a/apps/llm/assets/icons/mic_icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/apps/llm/assets/icons/pause_icon.svg b/apps/llm/assets/icons/pause_icon.svg deleted file mode 100644 index 2f5164d284..0000000000 --- a/apps/llm/assets/icons/pause_icon.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/apps/llm/assets/icons/send_icon.svg b/apps/llm/assets/icons/send_icon.svg deleted file mode 100644 index dfe227a3b2..0000000000 --- a/apps/llm/assets/icons/send_icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/apps/llm/assets/icons/splash.png b/apps/llm/assets/icons/splash.png deleted file mode 100644 index 0e89705a94..0000000000 Binary files a/apps/llm/assets/icons/splash.png and /dev/null differ diff --git a/apps/llm/assets/icons/stop_icon.svg b/apps/llm/assets/icons/stop_icon.svg deleted file mode 100644 index cb051899fe..0000000000 --- a/apps/llm/assets/icons/stop_icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/apps/llm/assets/icons/swm_icon.svg b/apps/llm/assets/icons/swm_icon.svg deleted file mode 100644 index 8c62f039be..0000000000 --- a/apps/llm/assets/icons/swm_icon.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/apps/llm/babel.config.js b/apps/llm/babel.config.js deleted file mode 100644 index d872de3f55..0000000000 --- a/apps/llm/babel.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = function (api) { - api.cache(true); - return { - presets: ['babel-preset-expo'], - plugins: ['react-native-reanimated/plugin'], - }; -}; diff --git a/apps/llm/colors.ts b/apps/llm/colors.ts deleted file mode 100644 index 03defcda9e..0000000000 --- a/apps/llm/colors.ts +++ /dev/null @@ -1,11 +0,0 @@ -const ColorPalette = { - primary: '#001A72', - seaBlueLight: '#E1F3FA', - seaBlueMedium: '#B5E1F1', - seaBlueDark: '#87CCE8', - blueLight: '#C1C6E5', - blueDark: '#6676AA', - strongPrimary: '#020F3C', -}; - -export default ColorPalette; diff --git a/apps/llm/components/AnimatedChatLoading.tsx b/apps/llm/components/AnimatedChatLoading.tsx deleted file mode 100644 index 2a5641fd39..0000000000 --- a/apps/llm/components/AnimatedChatLoading.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { StyleSheet, View } from 'react-native'; -import Animated, { - interpolateColor, - useAnimatedStyle, - useSharedValue, - withRepeat, - withTiming, -} from 'react-native-reanimated'; -import ColorPalette from '../colors'; - -export default function AnimatedChatLoading() { - const progress = useSharedValue(0); - progress.value = withRepeat(withTiming(1, { duration: 500 }), -1, true); - - const animatedStyle = useAnimatedStyle(() => { - return { - backgroundColor: interpolateColor( - progress.value, - [0, 1], - [ - ColorPalette.seaBlueLight, - ColorPalette.seaBlueMedium, - ColorPalette.seaBlueDark, - ] - ), - }; - }); - - return ( - - - - - - ); -} - -const styles = StyleSheet.create({ - messageLoadingContainer: { - flex: 1, - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - }, - loadingDot: { - width: 8, - height: 8, - borderRadius: 4, - }, -}); diff --git a/apps/llm/components/AudioWaveform.tsx b/apps/llm/components/AudioWaveform.tsx deleted file mode 100644 index cac4035614..0000000000 --- a/apps/llm/components/AudioWaveform.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { useMemo } from 'react'; -import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; -import ColorPalette from '../colors'; - -interface AudioWaveformProps { - buffer: Float32Array | null | undefined; - style?: StyleProp; -} - -const NUM_BARS = 32; - -export default function AudioWaveform({ buffer, style }: AudioWaveformProps) { - const bars = useMemo(() => { - if (!buffer || buffer.length === 0) return null; - const chunkSize = Math.max(1, Math.floor(buffer.length / NUM_BARS)); - const peaks: number[] = []; - let max = 0; - for (let i = 0; i < NUM_BARS; i++) { - const start = i * chunkSize; - const end = Math.min(start + chunkSize, buffer.length); - let peak = 0; - for (let j = start; j < end; j++) { - const v = Math.abs(buffer[j] ?? 0); - if (v > peak) peak = v; - } - peaks.push(peak); - if (peak > max) max = peak; - } - return max > 0 ? peaks.map((p) => p / max) : peaks; - }, [buffer]); - - if (!bars) return null; - - return ( - - {bars.map((amp, i) => ( - - ))} - - ); -} - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - alignItems: 'center', - height: 16, - minWidth: 160, - gap: 2, - }, - bar: { - flex: 1, - borderRadius: 1, - backgroundColor: ColorPalette.blueDark, - opacity: 0.35, - }, -}); diff --git a/apps/llm/components/ErrorBanner.tsx b/apps/llm/components/ErrorBanner.tsx deleted file mode 100644 index a5bebc504f..0000000000 --- a/apps/llm/components/ErrorBanner.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; - -interface ErrorBannerProps { - message: string | null; - onDismiss: () => void; -} - -export default function ErrorBanner({ message, onDismiss }: ErrorBannerProps) { - if (!message) return null; - - return ( - - - {message} - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - backgroundColor: '#FEE2E2', - borderLeftWidth: 4, - borderLeftColor: '#EF4444', - borderRadius: 8, - marginHorizontal: 16, - marginVertical: 8, - paddingVertical: 10, - paddingLeft: 12, - paddingRight: 8, - flexDirection: 'row', - alignItems: 'center', - }, - message: { - flex: 1, - color: '#991B1B', - fontSize: 14, - lineHeight: 20, - }, - closeButton: { - padding: 4, - marginLeft: 8, - }, - closeText: { - color: '#991B1B', - fontSize: 16, - fontWeight: '600', - }, -}); diff --git a/apps/llm/components/MarkdownComponent.tsx b/apps/llm/components/MarkdownComponent.tsx deleted file mode 100644 index 111019a363..0000000000 --- a/apps/llm/components/MarkdownComponent.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Platform } from 'react-native'; -import Markdown from 'react-native-markdown-display'; -import ColorPalette from '../colors'; - -interface MarkdownComponentProps { - text: string; -} - -export default function MarkdownComponent({ text }: MarkdownComponentProps) { - const fontSize = Platform.OS === 'ios' ? 16 : 14; - return ( - - {text} - - ); -} diff --git a/apps/llm/components/MessageItem.tsx b/apps/llm/components/MessageItem.tsx deleted file mode 100644 index cda8609885..0000000000 --- a/apps/llm/components/MessageItem.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React, { memo } from 'react'; -import { - View, - StyleSheet, - TouchableOpacity, - Text, - Image, - Platform, -} from 'react-native'; -import MarkdownComponent from './MarkdownComponent'; -import LlamaIcon from '../assets/icons/llama_icon.svg'; -import ColorPalette from '../colors'; -import { Message } from 'react-native-executorch'; -import AudioWaveform from './AudioWaveform'; - -interface MessageItemProps { - message: Message; - deleteMessage: () => void; -} - -const MessageItem = memo(({ message, deleteMessage }: MessageItemProps) => { - if (message.role === 'assistant') { - return ( - - - - - - - - - - ); - } - - return ( - - - - {message.mediaPath && ( - - )} - {message.audioWaveform && ( - - )} - - - - ); -}); - -const CloseButton = ({ - deleteMessage, - role, -}: { - deleteMessage: () => void; - role: string; -}) => { - return ( - - - - ); -}; - -export default MessageItem; - -const styles = StyleSheet.create({ - aiMessage: { - flexDirection: 'row', - maxWidth: '75%', - alignSelf: 'flex-start', - marginVertical: 8, - alignItems: 'center', - }, - aiMessageContent: { - flex: 1, - }, - userMessageWrapper: { - flexDirection: 'row-reverse', - marginRight: 8, - marginVertical: 8, - maxWidth: '75%', - alignSelf: 'flex-end', - alignItems: 'flex-start', - }, - userMessageBubble: { - flexDirection: 'column', - paddingHorizontal: 12, - paddingVertical: 8, - borderRadius: 8, - backgroundColor: ColorPalette.seaBlueLight, - }, - userMessageImage: { - width: 200, - height: 200, - borderRadius: 6, - marginBottom: 6, - }, - userMessageWaveform: { - marginBottom: 6, - }, - aiMessageIconContainer: { - backgroundColor: ColorPalette.seaBlueLight, - height: 32, - width: 32, - alignItems: 'center', - justifyContent: 'center', - borderRadius: 16, - marginHorizontal: 7, - }, - closeButton: { - borderRadius: 11, - backgroundColor: ColorPalette.blueLight, - alignItems: 'center', - justifyContent: 'center', - width: 22, - height: 22, - }, - closeButtonRight: { - marginLeft: 8, - }, - closeButtonLeft: { - marginRight: 8, - }, - buttonText: { - fontSize: Platform.OS === 'ios' ? 16 : 14, - color: '#000', - }, -}); diff --git a/apps/llm/components/Messages.tsx b/apps/llm/components/Messages.tsx deleted file mode 100644 index a1848c1deb..0000000000 --- a/apps/llm/components/Messages.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useRef } from 'react'; -import { ScrollView, StyleSheet, View, Text } from 'react-native'; -import AnimatedChatLoading from './AnimatedChatLoading'; -import LlamaIcon from '../assets/icons/llama_icon.svg'; -import ColorPalette from '../colors'; -import MessageItem from './MessageItem'; -import { Message } from 'react-native-executorch'; - -interface MessagesComponentProps { - chatHistory: Message[]; - llmResponse: string; - isGenerating: boolean; - deleteMessage: (index: number) => void; -} - -export default function Messages({ - chatHistory, - llmResponse, - isGenerating, - deleteMessage, -}: MessagesComponentProps) { - const scrollViewRef = useRef(null); - - return ( - - scrollViewRef?.current?.scrollToEnd()} - > - true}> - {chatHistory.map((message, index) => ( - deleteMessage(index)} - /> - ))} - {isGenerating && ( - - - - - {!llmResponse ? ( - - - - ) : ( - {llmResponse.trim()} - )} - - )} - - - - ); -} - -const styles = StyleSheet.create({ - chatContainer: { flex: 1, width: '100%' }, - aiMessage: { - flexDirection: 'row', - maxWidth: '80%', - alignSelf: 'flex-start', - marginVertical: 8, - }, - messageLoadingContainer: { width: 28 }, - aiMessageIconContainer: { - backgroundColor: ColorPalette.seaBlueLight, - height: 32, - width: 32, - alignItems: 'center', - justifyContent: 'center', - borderRadius: 16, - marginHorizontal: 7, - }, - messageText: { - fontSize: 14, - lineHeight: 19.6, - color: ColorPalette.primary, - fontFamily: 'regular', - }, -}); diff --git a/apps/llm/components/ModelPicker.tsx b/apps/llm/components/ModelPicker.tsx deleted file mode 100644 index 94a848596e..0000000000 --- a/apps/llm/components/ModelPicker.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { - Dimensions, - Modal, - ScrollView, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; - -export type ModelOption = { - label: string; - value: T; -}; - -type Props = { - models: ModelOption[]; - selectedModel: T; - onSelect: (model: T) => void; - label?: string; - disabled?: boolean; -}; - -const DROPDOWN_MAX_HEIGHT = 200; - -export function ModelPicker({ - models, - selectedModel, - onSelect, - label, - disabled, -}: Props) { - const [open, setOpen] = useState(false); - const [triggerHeight, setTriggerHeight] = useState(0); - const [expandUp, setExpandUp] = useState(false); - const [dropdownTop, setDropdownTop] = useState(0); - const triggerRef = useRef>(null); - const selected = models.find((m) => m.value === selectedModel); - - useEffect(() => { - if (disabled) setOpen(false); - }, [disabled]); - - const handlePress = () => { - if (disabled) return; - if (open) { - setOpen(false); - return; - } - triggerRef.current?.measure( - ( - _x: number, - _y: number, - _width: number, - height: number, - _pageX: number, - pageY: number - ) => { - setTriggerHeight(height); - const spaceBelow = Dimensions.get('window').height - (pageY + height); - setExpandUp(spaceBelow < DROPDOWN_MAX_HEIGHT); - setDropdownTop(pageY); - setOpen(true); - } - ); - }; - - const dropdownStylePosition = expandUp - ? { - bottom: Dimensions.get('window').height - dropdownTop, - left: 12, - right: 12, - } - : { - top: dropdownTop + triggerHeight + 2, - left: 12, - right: 12, - }; - - return ( - <> - - - {label && {label}} - {selected?.label ?? '—'} - {open ? '▲' : '▼'} - - - - {open && ( - setOpen(false)} - animationType="none" - > - setOpen(false)} - /> - - - {models.map((item) => { - const isSelected = item.value === selectedModel; - return ( - { - onSelect(item.value); - setOpen(false); - }} - activeOpacity={0.7} - > - - {item.label} - - - ); - })} - - - - )} - - ); -} - -const styles = StyleSheet.create({ - container: { - marginHorizontal: 12, - marginVertical: 4, - alignSelf: 'stretch', - zIndex: 100, - }, - trigger: { - flexDirection: 'row', - alignItems: 'center', - borderWidth: 1, - borderColor: '#C1C6E5', - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 10, - backgroundColor: '#f5f5f5', - }, - triggerDisabled: { - opacity: 0.4, - }, - label: { - fontSize: 12, - color: '#888', - marginRight: 6, - }, - triggerText: { - flex: 1, - fontSize: 14, - color: '#001A72', - fontWeight: '500', - }, - chevron: { - fontSize: 10, - color: '#888', - marginLeft: 6, - }, - modalBackdrop: { - flex: 1, - backgroundColor: 'rgba(0, 0, 0, 0.3)', - }, - dropdown: { - position: 'absolute', - borderWidth: 1, - borderColor: '#C1C6E5', - borderRadius: 8, - backgroundColor: '#fff', - maxHeight: DROPDOWN_MAX_HEIGHT, - zIndex: 1000, - elevation: 5, - shadowColor: '#000', - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.15, - shadowRadius: 6, - }, - option: { - paddingHorizontal: 12, - paddingVertical: 10, - borderBottomWidth: 1, - borderBottomColor: '#f0f0f0', - }, - optionSelected: { - backgroundColor: '#e8ecf8', - }, - optionText: { - fontSize: 14, - color: '#333', - }, - optionTextSelected: { - color: '#001A72', - fontWeight: '600', - }, -}); diff --git a/apps/llm/components/Spinner.tsx b/apps/llm/components/Spinner.tsx deleted file mode 100644 index f436869ab8..0000000000 --- a/apps/llm/components/Spinner.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { View, Text, StyleSheet, ActivityIndicator, Modal } from 'react-native'; -import ColorPalette from '../colors'; - -interface SpinnerProps { - visible: boolean; - textContent: string; -} - -const Spinner = ({ visible, textContent }: SpinnerProps) => { - return ( - - - - - {textContent} - - - - ); -}; - -const styles = StyleSheet.create({ - overlay: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: 'rgba(0, 0, 0, 0.1)', - }, - container: { - padding: 25, - alignItems: 'center', - justifyContent: 'center', - }, - text: { - marginTop: 15, - color: ColorPalette.primary, - fontSize: 18, - fontWeight: 'bold', - }, -}); - -export default Spinner; diff --git a/apps/llm/components/StatsBar.tsx b/apps/llm/components/StatsBar.tsx deleted file mode 100644 index 20bad6cfa1..0000000000 --- a/apps/llm/components/StatsBar.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; -import { LLMStats } from '../hooks/useLLMStats'; - -interface Props { - stats: LLMStats | null; -} - -export function StatsBar({ stats }: Props) { - if (!stats) return null; - - return ( - - TTFT: {stats.ttft} ms - · - {stats.tokensPerSec} tok/s - · - {stats.totalTokens} tokens - - ); -} - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - gap: 8, - paddingVertical: 6, - }, - stat: { - fontSize: 13, - color: '#334155', - fontWeight: '500', - }, - separator: { - fontSize: 13, - color: '#94A3B8', - }, -}); diff --git a/apps/llm/components/SuggestedPrompts.tsx b/apps/llm/components/SuggestedPrompts.tsx deleted file mode 100644 index 7f66379b48..0000000000 --- a/apps/llm/components/SuggestedPrompts.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; -import ColorPalette from '../colors'; - -interface Props { - prompts: string[]; - onSelect: (prompt: string) => void; -} - -export default function SuggestedPrompts({ prompts, onSelect }: Props) { - return ( - - {prompts.map((prompt, i) => ( - onSelect(prompt)} - > - {prompt} - - ))} - - ); -} - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'center', - gap: 8, - paddingHorizontal: 16, - paddingTop: 16, - }, - chip: { - borderWidth: 1, - borderColor: ColorPalette.blueLight, - borderRadius: 20, - paddingHorizontal: 14, - paddingVertical: 8, - backgroundColor: '#fafbff', - }, - chipText: { - fontFamily: 'regular', - fontSize: 13, - color: ColorPalette.primary, - }, -}); diff --git a/apps/llm/components/llmModels.ts b/apps/llm/components/llmModels.ts deleted file mode 100644 index 58b8c01d74..0000000000 --- a/apps/llm/components/llmModels.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { - models, - LLAMA3_2_1B_QLORA, - LLAMA3_2_3B_QLORA, - LLMProps, -} from 'react-native-executorch'; -import { ModelOption } from './ModelPicker'; -const llm = models.llm; - -export type LLMModelSources = LLMProps['model']; - -export const LLM_MODELS: ModelOption[] = [ - //Gemma 4 - { - label: 'Gemma 4 E2B', - value: llm.gemma4_e2b(), - }, - // Llama 3.2 - { - label: 'Llama 3.2 1B', - value: llm.llama3_2_1b({ quant: false }), - }, - { label: 'Llama 3.2 1B QLoRA', value: LLAMA3_2_1B_QLORA }, - { label: 'Llama 3.2 1B SpinQuant', value: llm.llama3_2_1b() }, - { - label: 'Llama 3.2 3B', - value: llm.llama3_2_3b({ quant: false }), - }, - { label: 'Llama 3.2 3B QLoRA', value: LLAMA3_2_3B_QLORA }, - { label: 'Llama 3.2 3B SpinQuant', value: llm.llama3_2_3b() }, - // Qwen3 - { - label: 'Qwen3 0.6B', - value: llm.qwen3_0_6b({ quant: false }), - }, - { label: 'Qwen3 0.6B Quantized', value: llm.qwen3_0_6b() }, - { - label: 'Qwen3 1.7B', - value: llm.qwen3_1_7b({ quant: false }), - }, - { label: 'Qwen3 1.7B Quantized', value: llm.qwen3_1_7b() }, - { label: 'Qwen3 4B', value: llm.qwen3_4b({ quant: false }) }, - { label: 'Qwen3 4B Quantized', value: llm.qwen3_4b() }, - // Hammer 2.1 - { - label: 'Hammer 2.1 0.5B', - value: llm.hammer2_1_0_5b({ quant: false }), - }, - { - label: 'Hammer 2.1 0.5B Quantized', - value: llm.hammer2_1_0_5b(), - }, - { - label: 'Hammer 2.1 1.5B', - value: llm.hammer2_1_1_5b({ quant: false }), - }, - { - label: 'Hammer 2.1 1.5B Quantized', - value: llm.hammer2_1_1_5b(), - }, - { - label: 'Hammer 2.1 3B', - value: llm.hammer2_1_3b({ quant: false }), - }, - { - label: 'Hammer 2.1 3B Quantized', - value: llm.hammer2_1_3b(), - }, - // SmolLM2 - { - label: 'SmolLM2 135M', - value: llm.smollm2_1_135m({ quant: false }), - }, - { - label: 'SmolLM2 135M Quantized', - value: llm.smollm2_1_135m(), - }, - { - label: 'SmolLM2 360M', - value: llm.smollm2_1_360m({ quant: false }), - }, - { - label: 'SmolLM2 360M Quantized', - value: llm.smollm2_1_360m(), - }, - { - label: 'SmolLM2 1.7B', - value: llm.smollm2_1_1_7b({ quant: false }), - }, - { - label: 'SmolLM2 1.7B Quantized', - value: llm.smollm2_1_1_7b(), - }, - // Qwen2.5 - { - label: 'Qwen2.5 0.5B', - value: llm.qwen2_5_0_5b({ quant: false }), - }, - { - label: 'Qwen2.5 0.5B Quantized', - value: llm.qwen2_5_0_5b(), - }, - { - label: 'Qwen2.5 1.5B', - value: llm.qwen2_5_1_5b({ quant: false }), - }, - { - label: 'Qwen2.5 1.5B Quantized', - value: llm.qwen2_5_1_5b(), - }, - { - label: 'Qwen2.5 3B', - value: llm.qwen2_5_3b({ quant: false }), - }, - { label: 'Qwen2.5 3B Quantized', value: llm.qwen2_5_3b() }, - // Qwen3.5 - { label: 'Qwen3.5 0.8B Quantized', value: llm.qwen3_5_0_8b() }, - { label: 'Qwen3.5 2B Quantized', value: llm.qwen3_5_2b() }, - // Phi-4 - { - label: 'Phi-4 Mini 4B', - value: llm.phi_4_mini_4b({ quant: false }), - }, - { - label: 'Phi-4 Mini 4B Quantized', - value: llm.phi_4_mini_4b(), - }, - // LFM2.5 - { - label: 'LFM2.5 350M', - value: llm.lfm2_5_350m({ quant: false }), - }, - { label: 'LFM2.5 350M Quantized', value: llm.lfm2_5_350m() }, - { - label: 'LFM2.5 1.2B Instruct', - value: llm.lfm2_5_1_2b_instruct({ quant: false }), - }, - { - label: 'LFM2.5 1.2B Instruct Quantized', - value: llm.lfm2_5_1_2b_instruct(), - }, - // Bielik v3.0 - { - label: 'Bielik v3.0 1.5B', - value: llm.bielik_v3_0_1_5b({ quant: false }), - }, - { - label: 'Bielik v3.0 1.5B Quantized', - value: llm.bielik_v3_0_1_5b(), - }, -]; diff --git a/apps/llm/context.ts b/apps/llm/context.ts deleted file mode 100644 index 0eb0560768..0000000000 --- a/apps/llm/context.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createContext } from 'react'; - -export const GeneratingContext = createContext({ - setGlobalGenerating: (_newState: boolean) => {}, -}); diff --git a/apps/llm/declarations.d.ts b/apps/llm/declarations.d.ts deleted file mode 100644 index 85e178f497..0000000000 --- a/apps/llm/declarations.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module '*.svg' { - import { SvgProps } from 'react-native-svg'; - const content: React.FV; - export default content; -} diff --git a/apps/llm/hooks/useLLMStats.ts b/apps/llm/hooks/useLLMStats.ts deleted file mode 100644 index 4cb6e7abdb..0000000000 --- a/apps/llm/hooks/useLLMStats.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; - -export interface LLMStats { - ttft: number; - tokensPerSec: number; - totalTokens: number; -} - -export function useLLMStats( - response: string, - isGenerating: boolean, - totalTokens: number -) { - const sendTimeRef = useRef(null); - const firstTokenTimeRef = useRef(null); - const lastResponseRef = useRef(''); - const [stats, setStats] = useState(null); - - useEffect(() => { - if (isGenerating && response.length > 0) { - lastResponseRef.current = response; - if (firstTokenTimeRef.current === null && sendTimeRef.current !== null) { - firstTokenTimeRef.current = Date.now(); - } - } - }, [response, isGenerating, totalTokens]); - - useEffect(() => { - if ( - !isGenerating && - sendTimeRef.current !== null && - firstTokenTimeRef.current !== null - ) { - const endTime = Date.now(); - const ttft = firstTokenTimeRef.current - sendTimeRef.current; - const totalTime = (endTime - firstTokenTimeRef.current) / 1000; - const tokensPerSec = - totalTime > 0 ? Math.round(totalTokens / totalTime) : 0; - setStats({ ttft, tokensPerSec, totalTokens }); - sendTimeRef.current = null; - firstTokenTimeRef.current = null; - } - }, [isGenerating, totalTokens]); - - const onMessageSend = () => { - sendTimeRef.current = Date.now(); - firstTokenTimeRef.current = null; - lastResponseRef.current = ''; - setStats(null); - }; - - return { stats, onMessageSend }; -} diff --git a/apps/llm/index.ts b/apps/llm/index.ts deleted file mode 100644 index 3f443dcf95..0000000000 --- a/apps/llm/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { registerRootComponent } from 'expo'; - -import App from './app'; - -// registerRootComponent calls AppRegistry.registerComponent('main', () => App); -// It also ensures that whether you load the app in Expo Go or in a native build, -// the environment is set up appropriately -registerRootComponent(App); diff --git a/apps/llm/metro.config.js b/apps/llm/metro.config.js deleted file mode 100644 index f58bbc2131..0000000000 --- a/apps/llm/metro.config.js +++ /dev/null @@ -1,19 +0,0 @@ -const { getDefaultConfig } = require('expo/metro-config'); - -const config = getDefaultConfig(__dirname); - -const { transformer, resolver } = config; - -config.transformer = { - ...transformer, - babelTransformerPath: require.resolve('react-native-svg-transformer/expo'), -}; -config.resolver = { - ...resolver, - assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'), - sourceExts: [...resolver.sourceExts, 'svg'], -}; - -config.resolver.assetExts.push('pte'); - -module.exports = config; diff --git a/apps/llm/package.json b/apps/llm/package.json deleted file mode 100644 index daca936e36..0000000000 --- a/apps/llm/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "llm", - "version": "1.0.0", - "main": "expo-router/entry", - "scripts": { - "start": "expo start", - "android": "expo run:android", - "ios": "expo run:ios", - "web": "expo start --web", - "typecheck": "tsc", - "lint": "eslint . --ext .ts,.tsx --fix" - }, - "dependencies": { - "@expo/log-box": "~56.0.12", - "@react-native/metro-config": "^0.85.3", - "@react-navigation/drawer": "^7.9.4", - "@react-navigation/native": "^7.2.2", - "expo": "^56.0.8", - "expo-brightness": "~56.0.5", - "expo-build-properties": "~56.0.16", - "expo-calendar": "~56.0.8", - "expo-constants": "~56.0.16", - "expo-document-picker": "~56.0.4", - "expo-font": "~56.0.5", - "expo-linking": "~56.0.13", - "expo-router": "~56.2.8", - "expo-status-bar": "~56.0.4", - "metro-config": "^0.84.0", - "react": "19.2.3", - "react-native": "0.85.3", - "react-native-audio-api": "0.12.2", - "react-native-device-info": "^15.0.2", - "react-native-executorch": "workspace:*", - "react-native-executorch-expo-resource-fetcher": "workspace:*", - "react-native-gesture-handler": "~2.31.1", - "react-native-image-picker": "^8.2.1", - "react-native-loading-spinner-overlay": "^3.0.1", - "react-native-markdown-display": "^7.0.2", - "react-native-reanimated": "~4.3.0", - "react-native-safe-area-context": "~5.7.0", - "react-native-screens": "~4.25.2", - "react-native-svg": "15.15.4", - "react-native-svg-transformer": "^1.5.3", - "react-native-worklets": "0.8.1" - }, - "devDependencies": { - "@babel/core": "^7.29.0", - "@types/react": "~19.2.14", - "@types/react-refresh": "^0", - "babel-preset-expo": "~56.0.14", - "react-refresh": "^0.18.0" - }, - "private": true -} diff --git a/apps/llm/tsconfig.json b/apps/llm/tsconfig.json deleted file mode 100644 index bfe226172e..0000000000 --- a/apps/llm/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "strict": true, - "allowJs": true, - "module": "preserve", - "moduleDetection": "force", - "moduleResolution": "bundler", - "customConditions": ["react-native"], - "noEmit": true, - "paths": { - "react-native-executorch": ["../../packages/react-native-executorch/src"], - "react-native-executorch-expo-resource-fetcher": [ - "../../packages/expo-resource-fetcher/src" - ] - } - } -} diff --git a/apps/llm/utils/piiMatching.ts b/apps/llm/utils/piiMatching.ts deleted file mode 100644 index 1f4a8adf1e..0000000000 --- a/apps/llm/utils/piiMatching.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { PiiEntity } from 'react-native-executorch'; - -/** - * A detected entity span pinned to a character range in the source text. - */ -export interface EntityMatch { - start: number; - end: number; - label: string; -} - -const escapeRegex = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - -// Word-boundary-anchored search so e.g. "John" doesn't match the "John" -// inside "Johnson". -function findWordBounded(source: string, needle: string, from: number): number { - const re = new RegExp(`\\b${escapeRegex(needle)}\\b`); - const match = re.exec(source.slice(from)); - return match ? from + match.index : -1; -} - -/** - * Map detected entities (which carry decoded text only) onto character - * ranges in the source text by scanning forward. - * - * The native runner's `text` field is the BPE-detokenized form of the - * entity, which often differs from the source by whitespace, punctuation - * spacing, or stripped specials. Strategy: - * 1) Try a word-bounded match for the whole entity from the cursor onward. - * 2) On miss, fall back to each non-trivial word from the entity text - * individually so we still highlight most of the span. - * - * Order-preserving: cursor advances after each successful match so - * duplicate strings resolve left-to-right. - * @param source - The full text the model was run against. - * @param entities - Entities returned by the native runner. - * @returns Sorted, non-overlapping char-range matches. - */ -export function matchEntities( - source: string, - entities: PiiEntity[] -): EntityMatch[] { - const matches: EntityMatch[] = []; - let cursor = 0; - for (const e of entities) { - if (!e.text) continue; - const exact = findWordBounded(source, e.text, cursor); - if (exact !== -1) { - matches.push({ - start: exact, - end: exact + e.text.length, - label: e.label, - }); - cursor = exact + e.text.length; - continue; - } - const words = e.text.split(/\s+/).filter((w) => w.length > 1); - let localCursor = cursor; - for (const w of words) { - const idx = findWordBounded(source, w, localCursor); - if (idx === -1) continue; - matches.push({ start: idx, end: idx + w.length, label: e.label }); - localCursor = idx + w.length; - } - if (localCursor > cursor) cursor = localCursor; - } - matches.sort((a, b) => a.start - b.start); - return matches; -} - -export interface Segment { - text: string; - label: string | null; -} - -/** - * Slice the source text into alternating plain / labeled runs based on the - * matched entity ranges. Overlaps are dropped (the earlier match wins). - * @param source - The full text to slice. - * @param matches - Char-range matches from {@link matchEntities}. - * @returns An ordered array of segments covering the entire source. - */ -export function buildSegments( - source: string, - matches: EntityMatch[] -): Segment[] { - const segments: Segment[] = []; - let pos = 0; - for (const m of matches) { - if (m.start < pos) continue; - if (m.start > pos) { - segments.push({ text: source.slice(pos, m.start), label: null }); - } - segments.push({ text: source.slice(m.start, m.end), label: m.label }); - pos = m.end; - } - if (pos < source.length) { - segments.push({ text: source.slice(pos), label: null }); - } - return segments; -} - -/** - * Pastel color palette + stable label-to-color mapping. Same label always - * gets the same color across renders / runs. - */ -const PALETTE = [ - '#ffd4a8', - '#b8e1ff', - '#d4c5f9', - '#c3e8c3', - '#ffe8b8', - '#f8c6c6', - '#e3c8a8', - '#ff9aa2', - '#b6e3d4', - '#ffd6e0', - '#cdb4db', - '#ffc8a2', - '#a2d2ff', - '#bde0fe', - '#fcd5ce', -]; - -export function colorForLabel(label: string): string { - let hash = 0; - for (let i = 0; i < label.length; i++) { - // eslint-disable-next-line no-bitwise - hash = (hash * 31 + label.charCodeAt(i)) | 0; - } - return PALETTE[Math.abs(hash) % PALETTE.length] as string; -} diff --git a/apps/llm/utils/tools.ts b/apps/llm/utils/tools.ts deleted file mode 100644 index 765859ef87..0000000000 --- a/apps/llm/utils/tools.ts +++ /dev/null @@ -1,226 +0,0 @@ -import * as Calendar from 'expo-calendar'; -import * as Brightness from 'expo-brightness'; -import { PermissionStatus } from 'expo'; -import { clamp } from 'react-native-reanimated'; -import { Platform } from 'react-native'; -import { ToolCall } from 'react-native-executorch'; - -export const executeTool: (call: ToolCall) => Promise = async ( - call -) => { - switch (call.toolName) { - case 'brightness': - return await brightness(call); - case 'read_calendar': - return await readCalendar(call); - case 'add_event_to_calendar': - return await addEventToCalendar(call); - default: - console.error(`Wrong function! We don't handle it!`); - return null; - } -}; - -export const TOOL_DEFINITIONS_PHONE = [ - { - name: 'brightness', - description: - 'Change screen brightness. Change can be relative (higher/lower) or set to minimal or maximal.', - parameters: { - type: 'dict', - properties: { - relativeChange: { - type: 'number', - description: - 'Relative change of brightness (from 0 to 100). Change should be negative if user asks for less bright screen.', - }, - targetBrightness: { - type: 'number', - description: 'Relative change of brightness (from 0 to 100).', - }, - }, - }, - }, - { - name: 'read_calendar', - description: - 'Read calendar events from now up to given point in time. If nothing is specified leave both empty.', - parameters: { - type: 'dict', - properties: { - timeStart: { - type: 'string', - description: 'Date and time from which we want to read calendar.', - }, - timeEnd: { - type: 'string', - description: 'Date and time to which we want to read calendar.', - }, - }, - required: [], - }, - }, - { - name: 'add_event_to_calendar', - description: 'Schedules event in your calendar at given time.', - parameters: { - type: 'dict', - properties: { - time: { - type: 'string', - description: 'Date and time of an event.', - }, - title: { - type: 'string', - description: 'Title of an event', - }, - }, - required: ['time', 'title'], - }, - }, -]; - -const brightness = async (call: ToolCall) => { - console.log('Changing brightness!', call); - const { status } = await Brightness.getPermissionsAsync(); - if (status !== Brightness.PermissionStatus.GRANTED) { - return 'Brightness permission denied. Inform the user they need to grant brightness access in the app.'; - } - if ( - 'targetBrightness' in call.arguments && - typeof call.arguments.targetBrightness === 'number' - ) { - await Brightness.setBrightnessAsync(call.arguments.targetBrightness / 100); - } else if ( - 'relativeChange' in call.arguments && - typeof call.arguments.relativeChange === 'number' - ) { - await Brightness.setBrightnessAsync( - clamp( - (await Brightness.getBrightnessAsync()) + - call.arguments.relativeChange / 100, - 0, - 1 - ) - ); - } - return null; -}; - -const readCalendar = async (call: ToolCall) => { - console.log('Reading calendar!', call); - const { status } = await Calendar.getCalendarPermissionsAsync(); - if (status !== PermissionStatus.GRANTED) { - return 'Calendar permission denied. Inform the user they need to grant calendar access in the app.'; - } - - let startTime = Date.parse( - 'timeStart' in call.arguments && - typeof call.arguments.timeStart === 'string' - ? call.arguments.timeStart - : '' - ); - let endTime = Date.parse( - 'timeEnd' in call.arguments && typeof call.arguments.timeEnd === 'string' - ? call.arguments.timeEnd - : '' - ); - - if ( - startTime === endTime || - (Number.isNaN(startTime) && Number.isNaN(endTime)) - ) { - // default to today for empty function calls - let date; - if (Number.isNaN(startTime)) { - date = new Date(); - } else { - date = new Date(startTime); - } - // Set the time to 00:00:00 for the start of the day - startTime = new Date( - date.getFullYear(), - date.getMonth(), - date.getDate(), - 0, - 0, - 0 - ).valueOf(); - - // Set the time to 23:59:59 for the end of the day - endTime = new Date( - date.getFullYear(), - date.getMonth(), - date.getDate(), - 23, - 59, - 59 - ).valueOf(); - } else if (Number.isNaN(startTime) || Number.isNaN(endTime)) { - if (Number.isNaN(startTime)) { - const today = new Date(); - startTime = new Date( - today.getFullYear(), - today.getMonth(), - today.getDate(), - 0, - 0, - 0 - ).valueOf(); - } else if (Number.isNaN(endTime)) { - const today = new Date(); - - endTime = new Date( - today.getFullYear(), - today.getMonth(), - today.getDate(), - 23, - 59, - 59 - ).valueOf(); - } - } - const startDate = new Date(startTime); - const endDate = new Date(endTime); - const calendars = await Calendar.getCalendarsAsync( - Platform.OS === 'ios' ? Calendar.EntityTypes.EVENT : undefined - ); - const events = await Calendar.getEventsAsync( - calendars.map((calendar) => calendar.id), - startDate, - endDate - ); - - const eventsStringRepresentation = events.map( - (event) => `${event.title}, from: ${event.startDate}, to: ${event.endDate}` - ); - return eventsStringRepresentation.join('\n'); -}; - -const addEventToCalendar = async (call: ToolCall) => { - console.log('Adding event to calendar!', call); - const { status } = await Calendar.getCalendarPermissionsAsync(); - if (status !== PermissionStatus.GRANTED) { - return 'Calendar permission denied. Inform the user they need to grant calendar access in the app.'; - } - if ( - 'time' in call.arguments && - typeof call.arguments.time === 'string' && - 'title' in call.arguments && - typeof call.arguments.title === 'string' - ) { - const calendars = await Calendar.getCalendarsAsync( - Platform.OS === 'ios' ? Calendar.EntityTypes.EVENT : undefined - ); - let startDate = new Date(Date.parse(call.arguments.time)); - const endDate = new Date(startDate); - endDate.setHours(endDate.getHours() + 1); - - await Calendar.createEventAsync(calendars[0].id, { - title: call.arguments.title, - startDate: startDate, - endDate: endDate, - }); - } - return null; -}; diff --git a/apps/speech/.gitignore b/apps/speech/.gitignore deleted file mode 100644 index 4a2f68b457..0000000000 --- a/apps/speech/.gitignore +++ /dev/null @@ -1,39 +0,0 @@ -# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files - -# dependencies -node_modules/ - -# Expo -.expo/ -dist/ -web-build/ -expo-env.d.ts - -# Native -*.orig.* -*.jks -*.p8 -*.p12 -*.key -*.mobileprovision - -# Metro -.metro-health-check* - -# debug -npm-debug.* -yarn-debug.* -yarn-error.* - -# macOS -.DS_Store -*.pem - -# local env files -.env*.local - -# typescript -*.tsbuildinfo - -.yarn - diff --git a/apps/speech/App.tsx b/apps/speech/App.tsx deleted file mode 100644 index 3532fbce1a..0000000000 --- a/apps/speech/App.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React, { useState } from 'react'; -import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; -import { TextToSpeechScreen } from './screens/TextToSpeechScreen'; -import { SpeechToTextScreen } from './screens/SpeechToTextScreen'; -import { VoiceActivityDetectionScreen } from './screens/VoiceActivityDetectionScreen'; -import ColorPalette from './colors'; -import ExecutorchLogo from './assets/executorch.svg'; -import { Quiz } from './screens/Quiz'; -import { TextToSpeechLLMScreen } from './screens/TextToSpeechLLMScreen'; -import { initExecutorch } from 'react-native-executorch'; -import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher'; - -initExecutorch({ - resourceFetcher: ExpoResourceFetcher, -}); - -export default function App() { - const [currentScreen, setCurrentScreen] = useState< - | 'menu' - | 'speech-to-text' - | 'text-to-speech' - | 'quiz' - | 'text-to-speech-llm' - | 'vad' - >('menu'); - - const goToMenu = () => setCurrentScreen('menu'); - - if (currentScreen === 'text-to-speech') { - return ; - } - - if (currentScreen === 'speech-to-text') { - return ; - } - - if (currentScreen === 'vad') { - return ; - } - - if (currentScreen === 'quiz') { - return ; - } - - if (currentScreen === 'text-to-speech-llm') { - return ; - } - - return ( - - - Select a demo model - - setCurrentScreen('speech-to-text')} - > - Speech to Text - - setCurrentScreen('vad')} - > - Voice Activity Detection - - setCurrentScreen('text-to-speech')} - > - Text to Speech - - setCurrentScreen('quiz')} - > - Text to Speech - Quiz - - setCurrentScreen('text-to-speech-llm')} - > - Text to Speech - LLM Streaming - - - - ); -} - -export const fontSizes = { - xxl: 34, - xl: 22, - lg: 18, - md: 16, - sm: 14, - xs: 12, - xxs: 10, -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#fff', - }, - headerText: { - fontSize: fontSizes.lg, - color: ColorPalette.strongPrimary, - margin: 20, - }, - buttonContainer: { - width: '80%', - justifyContent: 'space-evenly', - marginBottom: 20, - }, - button: { - backgroundColor: ColorPalette.strongPrimary, - borderRadius: 8, - padding: 10, - alignItems: 'center', - marginBottom: 10, - }, - buttonText: { - color: 'white', - fontSize: fontSizes.md, - }, -}); diff --git a/apps/speech/app.json b/apps/speech/app.json deleted file mode 100644 index 170d1a20c7..0000000000 --- a/apps/speech/app.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "expo": { - "name": "speech", - "slug": "speech", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/icon.png", - "userInterfaceStyle": "light", - "newArchEnabled": true, - "splash": { - "image": "./assets/splash-icon.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, - "ios": { - "supportsTablet": true, - "bundleIdentifier": "com.anonymous.speech", - "infoPlist": { - "NSMicrophoneUsageDescription": "This app needs access to your microphone to record audio." - }, - "entitlements": { - "com.apple.developer.kernel.increased-memory-limit": true - } - }, - "android": { - "adaptiveIcon": { - "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#ffffff" - }, - "package": "com.anonymous.speech", - "permissions": [ - "android.permission.RECORD_AUDIO", - "android.permission.MODIFY_AUDIO_SETTINGS", - "android.permission.FOREGROUND_SERVICE", - "android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" - ] - }, - "web": { - "favicon": "./assets/favicon.png" - }, - "plugins": [ - "expo-font", - [ - "react-native-audio-api", - { - "iosBackgroundMode": true, - "iosMicrophonePermission": "This app requires access to the microphone to record audio.", - "androidPermissions": [ - "android.permission.MODIFY_AUDIO_SETTINGS", - "android.permission.FOREGROUND_SERVICE", - "android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK", - "android.permission.RECORD_AUDIO" - ], - "androidForegroundService": true, - "androidFSTypes": ["mediaPlayback", "microphone"] - } - ], - [ - "expo-build-properties", - { - "ios": { - "deploymentTarget": "17.0" - } - } - ] - ] - } -} diff --git a/apps/speech/assets/adaptive-icon.png b/apps/speech/assets/adaptive-icon.png deleted file mode 100644 index 03d6f6b6c6..0000000000 Binary files a/apps/speech/assets/adaptive-icon.png and /dev/null differ diff --git a/apps/speech/assets/executorch.svg b/apps/speech/assets/executorch.svg deleted file mode 100644 index e548ea4201..0000000000 --- a/apps/speech/assets/executorch.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/apps/speech/assets/favicon.png b/apps/speech/assets/favicon.png deleted file mode 100644 index e75f697b18..0000000000 Binary files a/apps/speech/assets/favicon.png and /dev/null differ diff --git a/apps/speech/assets/icon.png b/apps/speech/assets/icon.png deleted file mode 100644 index a0b1526fc7..0000000000 Binary files a/apps/speech/assets/icon.png and /dev/null differ diff --git a/apps/speech/assets/quiz-data.ts b/apps/speech/assets/quiz-data.ts deleted file mode 100644 index 71cfdc5445..0000000000 --- a/apps/speech/assets/quiz-data.ts +++ /dev/null @@ -1,864 +0,0 @@ -// --- Data --- -// cspell:ignoreRegExp /export const QUESTIONS = \[[\s\S]*?\];/ -export const QUESTIONS = [ - { - q: 'What is the capital of Japan?', - a: ['Beijing', 'Seoul', 'Tokyo', 'Kyoto'], - c: 2, - e: 'Tokyo is the capital of Japan.', - context: - "Tokyo, formerly known as Edo, became the capital of Japan in 1868. It is one of the world's most populous cities and a major center for finance, culture, and technology. Tokyo hosts the Imperial Palace and is famous for its blend of traditional and modern architecture.", - }, - { - q: 'Who wrote "Romeo and Juliet"?', - a: ['Mark Twain', 'William Shakespeare', 'Charles Dickens', 'Jane Austen'], - c: 1, - e: 'William Shakespeare wrote Romeo and Juliet.', - context: - 'William Shakespeare was an English playwright and poet, widely regarded as the greatest writer in the English language. "Romeo and Juliet" is one of his most famous tragedies, exploring themes of love, fate, and family conflict in Renaissance Italy.', - }, - { - q: 'Which planet has the most moons in our solar system?', - a: ['Mars', 'Earth', 'Jupiter', 'Venus'], - c: 2, - e: 'Jupiter has the most known moons.', - context: - 'Jupiter has over 90 known moons, including Ganymede, the largest moon in the solar system. Its strong gravity allows it to capture many objects as moons. Saturn also has many moons, but Jupiter currently holds the record.', - }, - { - q: 'What gas do plants use for photosynthesis?', - a: ['Oxygen', 'Carbon dioxide', 'Nitrogen', 'Helium'], - c: 1, - e: 'Plants use carbon dioxide for photosynthesis.', - context: - 'Photosynthesis is the process by which plants convert carbon dioxide and water into glucose and oxygen using sunlight. This process is essential for life on Earth, as it provides oxygen and food for many organisms.', - }, - { - q: 'Who painted the Sistine Chapel ceiling?', - a: ['Raphael', 'Leonardo da Vinci', 'Michelangelo', 'Donatello'], - c: 2, - e: 'Michelangelo painted the Sistine Chapel ceiling.', - context: - 'Michelangelo was an Italian Renaissance artist known for his sculptures and paintings. The Sistine Chapel ceiling, painted between 1508 and 1512, features scenes from the Book of Genesis and is considered a masterpiece of Western art.', - }, - { - q: 'What is the largest continent by land area?', - a: ['Africa', 'Asia', 'Europe', 'Antarctica'], - c: 1, - e: 'Asia is the largest continent by land area.', - context: - "Asia covers about 30% of Earth's land area and is home to more than half of the world's population. It includes diverse regions such as the Middle East, South Asia, East Asia, and Siberia.", - }, - { - q: 'Which element has the chemical symbol "O"?', - a: ['Gold', 'Oxygen', 'Silver', 'Iron'], - c: 1, - e: 'O is the symbol for oxygen.', - context: - "Oxygen is a vital element for life, making up about 21% of Earth's atmosphere. It is essential for respiration in most living organisms and is highly reactive, forming compounds with many other elements.", - }, - { - q: 'In which year did the first man land on the moon?', - a: ['1969', '1959', '1979', '1965'], - c: 0, - e: 'The first moon landing was in 1969.', - context: - 'Apollo 11 was the mission that first landed humans on the Moon. Neil Armstrong and Buzz Aldrin walked on the lunar surface, while Michael Collins orbited above. The event marked a major milestone in space exploration.', - }, - { - q: 'What is the smallest prime number?', - a: ['0', '1', '2', '3'], - c: 2, - e: '2 is the smallest prime number.', - context: - 'A prime number is a natural number greater than 1 that has no positive divisors other than 1 and itself. 2 is the only even prime number, as all other even numbers are divisible by 2.', - }, - { - q: 'Which ocean lies on the east coast of the United States?', - a: ['Pacific', 'Atlantic', 'Indian', 'Arctic'], - c: 1, - e: 'The Atlantic Ocean is on the east coast.', - context: - 'The Atlantic Ocean is the second-largest ocean and separates North America from Europe and Africa. Major cities like New York, Miami, and Boston are located along its coast.', - }, - { - q: 'What currency is used in the United Kingdom?', - a: ['Euro', 'Pound', 'Dollar', 'Franc'], - c: 1, - e: 'The British pound is the currency of the UK.', - context: - 'The pound sterling, commonly known as the pound, is the official currency of the United Kingdom. It is one of the oldest currencies still in use and is subdivided into 100 pence.', - }, - { - q: 'Who discovered penicillin?', - a: ['Alexander Fleming', 'Marie Curie', 'Louis Pasteur', 'Gregor Mendel'], - c: 0, - e: 'Alexander Fleming discovered penicillin.', - context: - 'Penicillin was the first true antibiotic discovered by Alexander Fleming in 1928. It has saved countless lives by effectively treating bacterial infections.', - }, - { - q: 'What is the human body organ that pumps blood?', - a: ['Liver', 'Lung', 'Heart', 'Kidney'], - c: 2, - e: 'The heart pumps blood in the body.', - context: - 'The heart is a muscular organ about the size of a fist, located slightly left of the center of the chest. It pumps blood through the circulatory system, supplying oxygen and nutrients to the body.', - }, - { - q: 'Which country is known for the pyramids at Giza?', - a: ['Mexico', 'Peru', 'Egypt', 'Sudan'], - c: 2, - e: 'Egypt is famous for the pyramids at Giza.', - context: - 'The Giza pyramid complex is one of the most famous archaeological sites in the world. The Great Pyramid of Giza is the largest pyramid in Egypt and one of the Seven Wonders of the Ancient World.', - }, - { - q: 'Which language has the most native speakers worldwide?', - a: ['English', 'Spanish', 'Mandarin', 'Hindi'], - c: 2, - e: 'Mandarin Chinese has the most native speakers.', - context: - 'Mandarin is the most widely spoken language in the world, with over a billion native speakers. It is the official language of China and Taiwan, and one of the official languages of Singapore.', - }, - { - q: 'What is H2O commonly called?', - a: ['Salt', 'Hydrogen', 'Water', 'Oxygen'], - c: 2, - e: 'H2O is the chemical formula for water.', - context: - "Water is essential for all known forms of life. It covers about 71% of Earth's surface and is vital for drinking, agriculture, and industry.", - }, - { - q: 'Which metal is liquid at room temperature?', - a: ['Mercury', 'Gold', 'Silver', 'Copper'], - c: 0, - e: 'Mercury is liquid at room temperature.', - context: - 'Mercury is the only metal that is liquid at standard conditions for temperature and pressure. It is used in thermometers, barometers, and some electrical switches.', - }, - { - q: 'What is the fastest land animal?', - a: ['Lion', 'Cheetah', 'Horse', 'Kangaroo'], - c: 1, - e: 'The cheetah is the fastest land animal.', - context: - 'The cheetah can reach speeds of up to 75 miles per hour (120 kilometers per hour) in short bursts covering distances up to 500 meters. It is built for speed with a lightweight body and long legs.', - }, - { - q: 'Which organ in plants makes food using sunlight?', - a: ['Root', 'Stem', 'Leaf', 'Flower'], - c: 2, - e: 'Leaves perform photosynthesis to make food.', - context: - 'Photosynthesis occurs in the chloroplasts of plant cells, which contain chlorophyll that captures sunlight. This process converts carbon dioxide and water into glucose and oxygen.', - }, - { - q: 'Who composed the Fifth Symphony known as "fate"?', - a: ['Mozart', 'Beethoven', 'Bach', 'Chopin'], - c: 1, - e: 'Beethoven composed the Fifth Symphony.', - context: - 'Ludwig van Beethoven was a German composer and pianist. His Fifth Symphony, composed between 1804 and 1808, is one of the most performed symphonies and is known for its distinctive four-note motif.', - }, - { - q: 'What is the boiling point of water at sea level in Celsius?', - a: ['90', '95', '100', '105'], - c: 2, - e: 'Water boils at 100 degrees Celsius at sea level.', - context: - 'The boiling point of water can change depending on the atmospheric pressure. At higher altitudes, water boils at a lower temperature due to reduced pressure.', - }, - { - q: 'Which city is known as the Big Apple?', - a: ['Los Angeles', 'Chicago', 'New York', 'Miami'], - c: 2, - e: 'New York City is nicknamed the Big Apple.', - context: - 'The nickname "The Big Apple" originally referred to New York City\'s horse racing tracks. It later became popularized in the 1970s and is now a widely recognized nickname for the city.', - }, - { - q: 'What is the longest river in the world by length?', - a: ['Nile', 'Amazon', 'Yangtze', 'Mississippi'], - c: 0, - e: 'The Nile has long been considered the longest.', - context: - 'The Nile River in Africa is approximately 6650 kilometers (4130 miles) long. It flows through eleven countries and is essential for agriculture and water supply in the region.', - }, - { - q: 'Which planet is closest to the Sun?', - a: ['Venus', 'Mercury', 'Earth', 'Mars'], - c: 1, - e: 'Mercury is the planet closest to the Sun.', - context: - 'Mercury is the smallest planet in our solar system and orbits the Sun at an average distance of about 57.91 million kilometers (36 million miles).', - }, - { - q: 'Who developed the theory of relativity?', - a: ['Isaac Newton', 'Albert Einstein', 'Niels Bohr', 'Galileo'], - c: 1, - e: 'Albert Einstein developed the theory of relativity.', - context: - "Einstein's theory of relativity, published in 1905, revolutionized our understanding of space, time, and gravity. It introduced the famous equation E=mc², linking energy and mass.", - }, - { - q: 'What is the main language spoken in Brazil?', - a: ['Spanish', 'Portuguese', 'French', 'English'], - c: 1, - e: 'Portuguese is the main language in Brazil.', - context: - "Brazil is the largest country in South America and the only one in the region where Portuguese is the official language. This is due to Brazil's colonization by Portugal in the 16th century.", - }, - { - q: 'What instrument has keys, pedals, and strings?', - a: ['Guitar', 'Violin', 'Piano', 'Flute'], - c: 2, - e: 'The piano has keys, pedals, and strings.', - context: - 'The piano is a musical instrument played by pressing keys that cause hammers to strike strings, producing sound. It is widely used in classical and popular music.', - }, - { - q: 'Which country gifted the Statue of Liberty to the USA?', - a: ['Germany', 'France', 'Italy', 'Spain'], - c: 1, - e: 'France gifted the Statue of Liberty to the USA.', - context: - 'The Statue of Liberty was a gift from the people of France to the United States, dedicated in 1886. It was designed by French sculptor Frédéric Auguste Bartholdi and symbolizes freedom and democracy.', - }, - { - q: "Which gas is most abundant in Earth's atmosphere?", - a: ['Oxygen', 'Carbon dioxide', 'Nitrogen', 'Argon'], - c: 2, - e: 'Nitrogen is the most abundant gas in the atmosphere.', - context: - "Nitrogen makes up about 78% of Earth's atmosphere by volume. It is a colorless, odorless gas that is essential for life, as it is a key component of amino acids and nucleic acids.", - }, - { - q: 'What is the chemical symbol for gold?', - a: ['Au', 'Ag', 'Gd', 'Go'], - c: 0, - e: 'Au is the chemical symbol for gold.', - context: - 'Gold is a dense, malleable metal with the chemical symbol Au (from Latin: aurum) and atomic number 79. It is highly valued for its use in jewelry, currency, and other arts.', - }, - { - q: 'Who painted "The Starry Night"?', - a: ['Paul Cezanne', 'Vincent van Gogh', 'Pablo Picasso', 'Claude Monet'], - c: 1, - e: 'Vincent van Gogh painted The Starry Night.', - context: - "The Starry Night is one of Vincent van Gogh's most famous paintings, created in 1889. It depicts a swirling night sky over a quiet town, expressing van Gogh's emotional turmoil and fascination with the night.", - }, - { - q: 'Which year did World War 2 end?', - a: ['1944', '1945', '1946', '1947'], - c: 1, - e: 'World War 2 ended in 1945.', - context: - "World War 2 was a global conflict that lasted from 1939 to 1945. It involved most of the world's nations and resulted in significant changes to the global political and social landscape.", - }, - { - q: 'What is the largest mammal?', - a: ['Elephant', 'Blue whale', 'Giraffe', 'Hippopotamus'], - c: 1, - e: 'The blue whale is the largest mammal.', - context: - 'The blue whale is the largest animal known to have ever existed, reaching lengths of up to 100 feet (30 meters) and weights of up to 200 tons. They are found in oceans worldwide and primarily eat small shrimp-like animals called krill.', - }, - { - q: 'Which element is needed to make steel?', - a: ['Carbon', 'Helium', 'Nitrogen', 'Neon'], - c: 0, - e: 'Carbon is combined with iron to make steel.', - context: - 'Steel is an alloy made primarily of iron and carbon. The carbon content determines the hardness and strength of the steel. Other elements may also be added to create different types of steel.', - }, - { - q: 'Who is the author of the Harry Potter series?', - a: ['C S Lewis', 'J R R Tolkien', 'J K Rowling', 'Philip Pullman'], - c: 2, - e: 'J K Rowling wrote the Harry Potter series.', - context: - 'The Harry Potter series is a globally popular fantasy book series written by J.K. Rowling. It follows the life and adventures of a young wizard, Harry Potter, and his friends.', - }, - { - q: 'Which country uses the Yen as its currency?', - a: ['China', 'Japan', 'South Korea', 'Vietnam'], - c: 1, - e: 'Japan uses the Yen as its currency.', - context: - 'The yen is the official currency of Japan, introduced in 1871. It is one of the most traded currencies in the world and is known for its stability.', - }, - { - q: 'Which vitamin is produced when skin is exposed to sunlight?', - a: ['Vitamin A', 'Vitamin B', 'Vitamin C', 'Vitamin D'], - c: 3, - e: 'Vitamin D is produced in skin after sunlight exposure.', - context: - 'Vitamin D is essential for maintaining healthy bones and teeth, and it plays a role in immune system function. The body produces vitamin D in response to skin being exposed to sunlight.', - }, - { - q: 'What is the tallest mountain in the world above sea level?', - a: ['K2', 'Kangchenjunga', 'Mount Everest', 'Lhotse'], - c: 2, - e: 'Mount Everest is the tallest above sea level.', - context: - "Mount Everest, located in the Himalayas on the border of Nepal and the Tibet Autonomous Region of China, is the Earth's highest mountain above sea level, with a peak at 8848.86 meters (29031.7 ft).", - }, - { - q: 'What device converts alternating current to direct current?', - a: ['Transformer', 'Rectifier', 'Generator', 'Inverter'], - c: 1, - e: 'A rectifier converts AC to DC.', - context: - 'A rectifier is an electrical device that converts alternating current (AC) to direct current (DC). It allows current to flow in one direction only, effectively converting the AC waveform to a DC waveform.', - }, - { - q: 'Which two colors make green when mixed in paint?', - a: ['Red and Blue', 'Blue and Yellow', 'Red and Yellow', 'Blue and Green'], - c: 1, - e: 'Blue and yellow mix to make green.', - context: - 'In color theory, blue and yellow are primary colors that, when mixed together, create green, which is a secondary color. This is due to the way our eyes perceive color and the way light wavelengths combine.', - }, - { - q: 'Who was the first president of United States?', - a: [ - 'Abraham Lincoln', - 'George Washington', - 'Thomas Jefferson', - 'John Adams', - ], - c: 1, - e: 'George Washington was the first president of United States.', - context: - 'George Washington was unanimously elected as the first President of the United States in 1788. He served two terms from 1789 to 1797 and is often called the "Father of His Country".', - }, - { - q: 'Which organ breaks down food and absorbs nutrients?', - a: ['Lung', 'Kidney', 'Stomach and intestine', 'Heart'], - c: 2, - e: 'Stomach and intestines digest and absorb nutrients.', - context: - 'The digestive system breaks down food into smaller molecules, which are then absorbed into the bloodstream through the walls of the intestines. The stomach and intestines play key roles in this process.', - }, - { - q: 'Which bird is known for its ability to mimic human speech?', - a: ['Eagle', 'Parrot', 'Sparrow', 'Ostrich'], - c: 1, - e: 'Parrots can mimic human speech.', - context: - 'Some species of parrots are known for their ability to imitate human speech and other sounds. This ability varies among individual birds and is thought to be a form of social learning.', - }, - { - q: 'What is the study of past human activity called?', - a: ['Anthropology', 'Archaeology', 'Sociology', 'Geology'], - c: 1, - e: 'Archaeology studies past human activity.', - context: - 'Archaeology is the scientific study of ancient cultures and human activity through the examination of artifacts, structures, and other physical remains.', - }, - { - q: 'Which substance makes up the majority of the Sun?', - a: ['Iron', 'Hydrogen', 'Carbon', 'Silicon'], - c: 1, - e: 'Hydrogen is the main element in the Sun.', - context: - 'The Sun is composed of about 74% hydrogen, 24% helium, and 2% heavier elements. Hydrogen is the primary fuel for the nuclear fusion reactions that power the Sun.', - }, - { - q: 'Who invented the telephone?', - a: [ - 'Thomas Edison', - 'Alexander Graham Bell', - 'Nikola Tesla', - 'Guglielmo Marconi', - ], - c: 1, - e: 'Alexander Graham Bell is credited with the telephone.', - context: - 'Alexander Graham Bell was a Scottish-born inventor, scientist, and teacher who is credited with inventing the first practical telephone. He was awarded the first US patent for the invention of the telephone.', - }, - { - q: 'What is the capital of Canada?', - a: ['Toronto', 'Montreal', 'Vancouver', 'Ottawa'], - c: 3, - e: 'Ottawa is the capital of Canada.', - context: - 'Ottawa is the capital city of Canada, located in the province of Ontario. It became the capital in 1857 and is home to many national institutions, including the Parliament of Canada.', - }, - { - q: 'Which planet is known for its rings?', - a: ['Mars', 'Jupiter', 'Saturn', 'Uranus'], - c: 2, - e: 'Saturn is famous for its rings.', - context: - "Saturn is the sixth planet from the Sun and is known for its prominent ring system, which is made up of ice particles, rocky debris, and dust. The rings are thought to be remnants of moons or comets that were torn apart by Saturn's gravity.", - }, - { - q: 'Which chemical is used as table salt?', - a: ['Sodium chloride', 'Potassium', 'Magnesium', 'Calcium'], - c: 0, - e: 'Table salt is sodium chloride.', - context: - 'Table salt is chemically known as sodium chloride (NaCl). It is composed of sodium ions and chloride ions and is used in food preparation and preservation.', - }, - { - q: 'Who painted "Guernica"?', - a: ['Salvador Dali', 'Pablo Picasso', 'Henri Matisse', 'Jackson Pollock'], - c: 1, - e: 'Pablo Picasso painted Guernica.', - context: - 'Guernica is a mural-sized oil painting on canvas by Spanish artist Pablo Picasso, completed in 1937. It is one of the most famous anti-war artworks, depicting the suffering caused by war and violence.', - }, - { - q: 'What is the largest desert in the world?', - a: ['Sahara', 'Gobi', 'Arabian', 'Antarctic desert'], - c: 3, - e: 'The Antarctic is the largest desert by area.', - context: - 'The Antarctic Desert is the largest desert in the world, covering an area of about 14 million square kilometers (5.5 million square miles). It is classified as a desert due to its extremely low humidity and precipitation.', - }, - { - q: 'Which sport uses a shuttlecock?', - a: ['Tennis', 'Badminton', 'Squash', 'Table tennis'], - c: 1, - e: 'Badminton uses a shuttlecock.', - context: - "Badminton is a racquet sport played using shuttlecocks and a lightweight racquet. The game is played on a rectangular court divided by a net, and the objective is to hit the shuttlecock over the net and into the opponent's court.", - }, - { - q: 'What is the freezing point of water in Fahrenheit?', - a: ['0', '32', '100', '212'], - c: 1, - e: 'Water freezes at 32 degrees Fahrenheit.', - context: - 'The freezing point of water is 32 degrees Fahrenheit (0 degrees Celsius) at standard atmospheric pressure. At this temperature, water molecules slow down and form a crystalline structure, resulting in ice.', - }, - { - q: 'Which continent has the most countries?', - a: ['Asia', 'Africa', 'Europe', 'South America'], - c: 1, - e: 'Africa has the most countries of any continent.', - context: - 'Africa is the second-largest and second-most populous continent, with 54 recognized sovereign states. It has a diverse range of cultures, languages, and ecosystems.', - }, - { - q: 'What is the main ingredient in traditional sushi?', - a: ['Beef', 'Rice', 'Potatoes', 'Cheese'], - c: 1, - e: 'Rice is the main ingredient in sushi.', - context: - 'Sushi is a Japanese dish typically made with vinegared rice, raw fish, and other ingredients like vegetables and seaweed. The rice is the essential component that defines sushi.', - }, - { - q: 'Which famous physicist wrote "A Brief History of Time"?', - a: ['Richard Feynman', 'Stephen Hawking', 'Carl Sagan', 'Brian Cox'], - c: 1, - e: 'Stephen Hawking wrote A Brief History of Time.', - context: - 'A Brief History of Time is a popular science book written by physicist Stephen Hawking. It explains complex concepts in cosmology, such as the Big Bang, black holes, and light cones, in accessible language.', - }, - { - q: 'Which city is the capital of Australia?', - a: ['Sydney', 'Melbourne', 'Canberra', 'Perth'], - c: 2, - e: 'Canberra is the capital of Australia.', - context: - 'Canberra is the capital city of Australia, located in the Australian Capital Territory. It was selected as the capital in 1908 as a compromise between rivals Sydney and Melbourne.', - }, - { - q: 'What is the largest organ in the human body?', - a: ['Liver', 'Skin', 'Heart', 'Brain'], - c: 1, - e: 'Skin is the largest organ of the human body.', - context: - "The skin is the body's largest organ, covering an area of about 2 square meters (22 square feet) in adults. It protects internal organs, regulates temperature, and enables the sense of touch.", - }, - { - q: 'Which gas do humans inhale to survive?', - a: ['Carbon dioxide', 'Nitrogen', 'Oxygen', 'Helium'], - c: 2, - e: 'Humans inhale oxygen to survive.', - context: - 'Oxygen is essential for human survival as it is required for cellular respiration, the process by which cells produce energy. Humans inhale air containing oxygen through the respiratory system.', - }, - { - q: 'Who developed the theory of evolution by natural selection?', - a: ['Gregor Mendel', 'Charles Darwin', 'Louis Pasteur', 'Alfred Wallace'], - c: 1, - e: 'Charles Darwin proposed natural selection.', - context: - 'Charles Darwin was an English naturalist, geologist, and biologist best known for his contributions to the science of evolution. He proposed the theory of natural selection as the mechanism of evolution.', - }, - { - q: 'What instrument measures temperature?', - a: ['Barometer', 'Thermometer', 'Hygrometer', 'Ammeter'], - c: 1, - e: 'A thermometer measures temperature.', - context: - 'A thermometer is a device that measures temperature, typically using a glass tube filled with mercury or alcohol that expands and contracts with temperature changes.', - }, - { - q: 'Which country has the largest population?', - a: ['India', 'United States', 'China', 'Russia'], - c: 2, - e: 'China has the largest population.', - context: - 'China is the most populous country in the world, with a population of over 1.4 billion people. It is followed by India, the United States, and Indonesia.', - }, - { - q: 'What is the chemical formula for table sugar (sucrose)?', - a: ['C6H12O6', 'C12H22O11', 'H2O', 'CO2'], - c: 1, - e: 'Sucrose has formula C12H22O11.', - context: - 'Table sugar, or sucrose, is a carbohydrate composed of glucose and fructose units. It is commonly used as a sweetener in food and drinks.', - }, - { - q: 'Which author wrote "Pride and Prejudice"?', - a: ['Emily Bronte', 'Charlotte Bronte', 'Jane Austen', 'Mary Shelley'], - c: 2, - e: 'Jane Austen wrote Pride and Prejudice.', - context: - 'Pride and Prejudice is a novel written by Jane Austen, published in 1813. It is a romantic fiction that critiques the British landed gentry at the end of the 18th century.', - }, - { - q: 'What is the largest island in the world?', - a: ['Greenland', 'Madagascar', 'Borneo', 'New Guinea'], - c: 0, - e: 'Greenland is the largest island.', - context: - "Greenland is the world's largest island that is not a continent. It is located between the Arctic and Atlantic Oceans and is an autonomous territory within the Kingdom of Denmark.", - }, - { - q: 'Which planet is known as the Red Planet?', - a: ['Earth', 'Mars', 'Venus', 'Mercury'], - c: 1, - e: 'Mars is known as the Red Planet.', - context: - 'Mars is often called the Red Planet because of its reddish appearance, which is due to iron oxide (rust) on its surface. It is the fourth planet from the Sun and has the largest dust storms in the solar system.', - }, - { - q: 'Who is credited with inventing the light bulb?', - a: ['Nikola Tesla', 'Thomas Edison', 'Alexander Graham Bell', 'James Watt'], - c: 1, - e: 'Thomas Edison is commonly credited for the light bulb.', - context: - 'Thomas Edison was an American inventor and businessman who is credited with developing the first commercially successful incandescent light bulb.', - }, - { - q: 'What is the capital of Italy?', - a: ['Milan', 'Naples', 'Rome', 'Florence'], - c: 2, - e: 'Rome is the capital of Italy.', - context: - 'Rome, the "Eternal City", is the capital of Italy and of the Lazio region. It is known for its nearly 3000 years of globally influential art, architecture, and culture.', - }, - { - q: 'Which metal has the highest electrical conductivity?', - a: ['Gold', 'Silver', 'Copper', 'Aluminum'], - c: 1, - e: 'Silver has the highest conductivity of common metals.', - context: - 'Silver is a metal known for its high electrical conductivity, thermal conductivity, and reflectivity. It is used in electrical contacts, conductors, and in various electronic devices.', - }, - { - q: 'What is the largest city in India by population?', - a: ['Delhi', 'Mumbai', 'Bangalore', 'Kolkata'], - c: 1, - e: 'Mumbai is the largest city by population in India.', - context: - 'Mumbai, formerly known as Bombay, is the most populous city in India and the seventh most populous city in the world. It is the financial, commercial, and entertainment hub of India.', - }, - { - q: 'Which mountain range includes Mount Kilimanjaro?', - a: [ - 'Andes', - 'Himalayas', - 'Kilimanjaro is a standalone mountain', - 'Rockies', - ], - c: 2, - e: 'Kilimanjaro is a free standing mountain, not part of a range.', - context: - 'Mount Kilimanjaro is a dormant stratovolcano located in Tanzania. It is the highest mountain in Africa, standing at 5895 meters (19341 feet) above sea level.', - }, - { - q: 'Which animal is known as the king of the jungle?', - a: ['Tiger', 'Elephant', 'Lion', 'Bear'], - c: 2, - e: 'The lion is often called the king of the jungle.', - context: - 'The lion is a large cat species found in Africa and India. It is known for its strength, courage, and majestic appearance. The term "king of the jungle" is a colloquial expression and lions actually inhabit grasslands and savannas.', - }, - { - q: 'What is the primary language of Egypt?', - a: ['Arabic', 'English', 'French', 'Greek'], - c: 0, - e: 'Arabic is the primary language in Egypt.', - context: - 'Arabic is the official language of Egypt and is spoken by the vast majority of the population. Egypt is also known for its ancient civilization and historical monuments.', - }, - { - q: 'Which country has the largest area in the world?', - a: ['United States', 'China', 'Russia', 'Canada'], - c: 2, - e: 'Russia is the largest country by area.', - context: - 'Russia is the largest country in the world by land area, covering more than 17 million square kilometers. It spans Eastern Europe and northern Asia, and has a wide range of environments and landscapes.', - }, - { - q: 'Who painted the Mona Lisa?', - a: ['Michelangelo', 'Leonardo da Vinci', 'Rembrandt', 'Raphael'], - c: 1, - e: 'Leonardo da Vinci painted the Mona Lisa.', - context: - 'The Mona Lisa is a half-length portrait painting by the Italian Renaissance artist Leonardo da Vinci. It is considered an archetypal masterpiece of the Italian Renaissance and is one of the most famous paintings in the world.', - }, - { - q: 'Which chemical element has atomic number 1?', - a: ['Helium', 'Hydrogen', 'Oxygen', 'Lithium'], - c: 1, - e: 'Hydrogen has atomic number 1.', - context: - 'Hydrogen is the chemical element with the symbol H and atomic number 1. It is the lightest and most abundant element in the universe, making up about 75% of its elemental mass.', - }, - { - q: 'What is the capital of Germany?', - a: ['Munich', 'Frankfurt', 'Berlin', 'Hamburg'], - c: 2, - e: 'Berlin is the capital of Germany.', - context: - 'Berlin is the capital and largest city of Germany, located in the northeastern part of the country. It is known for its cultural heritage, modern architecture, and vibrant arts scene.', - }, - { - q: 'Which famous scientist formulated the laws of motion?', - a: ['Albert Einstein', 'Isaac Newton', 'Galileo Galilei', 'Max Planck'], - c: 1, - e: 'Isaac Newton formulated the classical laws of motion.', - context: - 'Isaac Newton was an English mathematician, physicist, and astronomer who is widely recognized for formulating the laws of motion and universal gravitation.', - }, - { - q: 'What is the main ingredient in hummus?', - a: ['Lentils', 'Chickpeas', 'Beans', 'Peas'], - c: 1, - e: 'Hummus is made primarily from chickpeas.', - context: - 'Hummus is a spread made from cooked, mashed chickpeas or other beans, and is a common part of Levantine and Middle Eastern cuisines. It is often served with pita bread.', - }, - { - q: 'What is the largest lake by area in Africa?', - a: ['Lake Victoria', 'Lake Tanganyika', 'Lake Malawi', 'Lake Turkana'], - c: 0, - e: 'Lake Victoria is the largest lake in Africa by area.', - context: - 'Lake Victoria is the largest lake in Africa and the second-largest freshwater lake in the world by surface area. It is bordered by three countries: Tanzania, Uganda, and Kenya.', - }, - { - q: 'Which composer wrote the opera La Traviata?', - a: ['Wagner', 'Verdi', 'Puccini', 'Mozart'], - c: 1, - e: 'Giuseppe Verdi composed La Traviata.', - context: - 'La Traviata is an opera in three acts by Giuseppe Verdi, premiered in 1853. It is based on the novel "La Dame aux Camélias" by Alexandre Dumas fils and is one of the most performed operas worldwide.', - }, - { - q: 'Which city is home to the Colosseum?', - a: ['Athens', 'Rome', 'Istanbul', 'Naples'], - c: 1, - e: 'The Colosseum is located in Rome.', - context: - 'The Colosseum, also known as the Flavian Amphitheatre, is an ancient oval amphitheatre located in the center of Rome. It is the largest amphitheatre ever built and is considered one of the greatest works of Roman architecture and engineering.', - }, - { - q: 'What is the capital of Spain?', - a: ['Valencia', 'Seville', 'Madrid', 'Barcelona'], - c: 2, - e: 'Madrid is the capital of Spain.', - context: - 'Madrid is the capital and largest city of Spain, located in the center of the country. It is known for its cultural and artistic heritage, as well as its vibrant nightlife.', - }, - { - q: 'Which planet has a day longer than its year?', - a: ['Mercury', 'Venus', 'Mars', 'Earth'], - c: 1, - e: 'Venus rotates slowly so its day is longer than its year.', - context: - 'Venus is the second planet from the Sun and has a very slow rotation on its axis, taking about 243 Earth days to complete one rotation. However, its orbit around the Sun takes only about 225 Earth days.', - }, - { - q: 'Who wrote "The Odyssey"?', - a: ['Homer', 'Virgil', 'Sophocles', 'Plato'], - c: 0, - e: 'Homer is attributed as the author of The Odyssey.', - context: - 'The Odyssey is an ancient Greek epic poem attributed to Homer. It is one of the two major ancient Greek epic poems, the other being the Iliad, and it consists of 24 books.', - }, - { - q: 'Which organ is primarily responsible for detoxifying chemicals?', - a: ['Heart', 'Lung', 'Liver', 'Spleen'], - c: 2, - e: 'The liver detoxifies many chemicals in the body.', - context: - 'The liver is a vital organ that plays a key role in metabolism, digestion, and detoxification. It filters blood coming from the digestive tract and detoxifies chemicals, metabolizes drugs, and secretes bile.', - }, - { - q: 'Which country is famous for maple syrup?', - a: ['United States', 'Canada', 'Norway', 'Sweden'], - c: 1, - e: 'Canada is famous for maple syrup.', - context: - 'Canada is the largest producer of maple syrup in the world, accounting for about 71% of the global market share. Maple syrup is a traditional Canadian sweetener made from the sap of sugar maple trees.', - }, - { - q: 'What is the common name for sodium bicarbonate?', - a: ['Baking soda', 'Table salt', 'Vinegar', 'Baking powder'], - c: 0, - e: 'Sodium bicarbonate is known as baking soda.', - context: - 'Sodium bicarbonate, commonly known as baking soda, is a chemical compound with the formula NaHCO₃. It is used in baking as a leavening agent, and also has various household and industrial uses.', - }, - { - q: 'Which sea creature has eight arms?', - a: ['Shark', 'Octopus', 'Dolphin', 'Jellyfish'], - c: 1, - e: 'An octopus has eight arms.', - context: - 'Octopuses are marine animals known for their eight arms, which are lined with sensitive suckers. They are intelligent creatures and can change color and texture to blend in with their surroundings.', - }, - { - q: 'What is the capital of Russia?', - a: ['Saint Petersburg', 'Moscow', 'Novosibirsk', 'Sochi'], - c: 1, - e: 'Moscow is the capital of Russia.', - context: - 'Moscow is the capital and largest city of Russia, located in the western part of the country. It is known for its rich cultural history, architecture, and as a major political, economic, and scientific center.', - }, - { - q: 'Which instrument is primarily used in jazz as a brass reed instrument?', - a: ['Violin', 'Saxophone', 'Clarinet', 'Oboe'], - c: 1, - e: 'The saxophone is a common brass reed instrument in jazz.', - context: - 'The saxophone is a musical instrument invented by Adolphe Sax in the 1840s. It is a key instrument in jazz music, known for its expressive range and timbre.', - }, - { - q: 'What is the largest planet in our solar system?', - a: ['Earth', 'Saturn', 'Jupiter', 'Neptune'], - c: 2, - e: 'Jupiter is the largest planet in the solar system.', - context: - 'Jupiter is the fifth planet from the Sun and is more than twice as massive as all the other planets in the solar system combined. It has a thick atmosphere made up mostly of hydrogen and helium.', - }, - { - q: 'Which chemical element is liquid and used in thermometers?', - a: ['Mercury', 'Lead', 'Sodium', 'Iron'], - c: 0, - e: 'Mercury is used in some thermometers.', - context: - 'Mercury is a chemical element with the symbol Hg and atomic number 80. It is a heavy, silvery-white liquid metal that is used in thermometers, barometers, and other scientific instruments.', - }, - { - q: 'Who is known as the father of modern physics and developed the laws of motion?', - a: ['Benjamin Franklin', 'Isaac Newton', 'Albert Einstein', 'Nikola Tesla'], - c: 1, - e: 'Isaac Newton developed the laws of motion.', - context: - 'Isaac Newton is often referred to as the father of modern physics for his groundbreaking work in the 17th century. He formulated the laws of motion and universal gravitation, laying the foundation for classical mechanics.', - }, - { - q: 'Which country is famous for the tango dance?', - a: ['Brazil', 'Argentina', 'Mexico', 'Chile'], - c: 1, - e: 'Argentina is famous for the tango.', - context: - 'The tango is a partner dance that originated in the 1880s along the River Plate, the natural border between Argentina and Uruguay. It is now popular worldwide and is known for its passionate and dramatic style.', - }, - { - q: 'What is the capital of South Africa (one of them)?', - a: ['Cape Town', 'Pretoria', 'Johannesburg', 'Durban'], - c: 1, - e: "Pretoria is one of South Africa's capitals (administrative).", - context: - 'South Africa has three capital cities: Pretoria (administrative), Cape Town (legislative), and Bloemfontein (judicial). Pretoria is known for its diplomatic missions and embassies.', - }, - { - q: 'Which substance is needed for combustion?', - a: ['Oxygen', 'Helium', 'Nitrogen', 'Carbon dioxide'], - c: 0, - e: 'Oxygen supports combustion.', - context: - 'Combustion is a chemical process that occurs when a substance reacts rapidly with oxygen and releases energy in the form of light and heat. Oxygen is essential for combustion to occur.', - }, - { - q: 'Who wrote the novel "Moby Dick"?', - a: [ - 'Herman Melville', - 'Ernest Hemingway', - 'F Scott Fitzgerald', - 'Mark Twain', - ], - c: 0, - e: 'Herman Melville is the author of Moby Dick.', - context: - "Moby Dick is an 1851 novel by Herman Melville. The book is the sailor Ishmael's narrative of the obsessive quest of Ahab, captain of the whaling ship Pequod, for revenge on Moby Dick.", - }, - { - q: 'What is the primary material used to make glass?', - a: ['Iron', 'Sand', 'Wood', 'Clay'], - c: 1, - e: 'Glass is primarily made from silica sand.', - context: - 'Glass is a solid material that is typically made from silica (silicon dioxide) sand, soda ash, and limestone. It is used in windows, bottles, and many other applications.', - }, - { - q: 'Which famous tower leans and is in Italy?', - a: ['Eiffel Tower', 'Leaning Tower of Pisa', 'Big Ben', 'CN Tower'], - c: 1, - e: 'The Leaning Tower of Pisa is in Italy.', - context: - 'The Leaning Tower of Pisa is a freestanding bell tower located in Pisa, Italy. It is famous for its unintended tilt, which began during its construction in the 12th century.', - }, - { - q: 'Which organ controls the nervous system?', - a: ['Heart', 'Brain', 'Liver', 'Kidney'], - c: 1, - e: 'The brain controls the nervous system.', - context: - 'The brain is the central organ of the human nervous system, and with the spinal cord, it makes up the central nervous system (CNS). It is responsible for processing sensory information and coordinating bodily functions.', - }, - { - q: 'What is the capital of Turkey?', - a: ['Istanbul', 'Ankara', 'Izmir', 'Bursa'], - c: 1, - e: 'Ankara is the capital of Turkey.', - context: - 'Ankara is the capital of Turkey, located in the central part of the country. It became the capital in 1923, replacing Istanbul as the capital of the Republic of Turkey.', - }, - { - q: 'Which sport is played at Wimbledon?', - a: ['Cricket', 'Tennis', 'Football', 'Rugby'], - c: 1, - e: 'Wimbledon is a major tennis tournament.', - context: - 'The Wimbledon Championships is the oldest tennis tournament in the world, and is considered the most prestigious. It has been held at the All England Club in Wimbledon, London, since 1877.', - }, - { - q: 'What metal is primarily used to make aircraft bodies due to its light weight?', - a: ['Steel', 'Titanium', 'Aluminum', 'Copper'], - c: 2, - e: 'Aluminum is widely used for aircraft bodies.', - context: - 'Aluminum is a lightweight, durable metal that is resistant to corrosion, making it ideal for aircraft construction. It is used in the manufacture of aircraft bodies, wings, and other components.', - }, - { - q: 'Which ocean is between Africa and Australia?', - a: ['Atlantic', 'Pacific', 'Indian', 'Arctic'], - c: 2, - e: 'The Indian Ocean lies between Africa and Australia.', - context: - "The Indian Ocean is the third-largest ocean, covering about 20% of the Earth's water surface. It is bounded by Africa, Asia, Australia, and the Indian subcontinent.", - }, -]; diff --git a/apps/speech/assets/splash-icon.png b/apps/speech/assets/splash-icon.png deleted file mode 100644 index 03d6f6b6c6..0000000000 Binary files a/apps/speech/assets/splash-icon.png and /dev/null differ diff --git a/apps/speech/assets/swm_icon.svg b/apps/speech/assets/swm_icon.svg deleted file mode 100644 index 8c62f039be..0000000000 --- a/apps/speech/assets/swm_icon.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/apps/speech/colors.ts b/apps/speech/colors.ts deleted file mode 100644 index feb75ac336..0000000000 --- a/apps/speech/colors.ts +++ /dev/null @@ -1,6 +0,0 @@ -const ColorPalette = { - primary: '#001A72', - strongPrimary: '#020F3C', -}; - -export default ColorPalette; diff --git a/apps/speech/components/ErrorBanner.tsx b/apps/speech/components/ErrorBanner.tsx deleted file mode 100644 index a5bebc504f..0000000000 --- a/apps/speech/components/ErrorBanner.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; - -interface ErrorBannerProps { - message: string | null; - onDismiss: () => void; -} - -export default function ErrorBanner({ message, onDismiss }: ErrorBannerProps) { - if (!message) return null; - - return ( - - - {message} - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - backgroundColor: '#FEE2E2', - borderLeftWidth: 4, - borderLeftColor: '#EF4444', - borderRadius: 8, - marginHorizontal: 16, - marginVertical: 8, - paddingVertical: 10, - paddingLeft: 12, - paddingRight: 8, - flexDirection: 'row', - alignItems: 'center', - }, - message: { - flex: 1, - color: '#991B1B', - fontSize: 14, - lineHeight: 20, - }, - closeButton: { - padding: 4, - marginLeft: 8, - }, - closeText: { - color: '#991B1B', - fontSize: 16, - fontWeight: '600', - }, -}); diff --git a/apps/speech/components/ModelPicker.tsx b/apps/speech/components/ModelPicker.tsx deleted file mode 100644 index f06d156329..0000000000 --- a/apps/speech/components/ModelPicker.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { - Dimensions, - Modal, - ScrollView, - StyleSheet, - Text, - TouchableOpacity, - TouchableWithoutFeedback, - View, -} from 'react-native'; - -export type ModelOption = { - label: string; - value: T; -}; - -type Props = { - models: ModelOption[]; - selectedModel: T; - onSelect: (model: T) => void; - label?: string; - disabled?: boolean; -}; - -const DROPDOWN_MAX_HEIGHT = 300; - -export function ModelPicker({ - models, - selectedModel, - onSelect, - label, - disabled, -}: Props) { - const [open, setOpen] = useState(false); - const [dropdownLayout, setDropdownLayout] = useState({ - x: 0, - y: 0, - width: 0, - }); - const triggerRef = useRef>(null); - const selected = models.find((m) => m.value === selectedModel); - - useEffect(() => { - if (disabled) setOpen(false); - }, [disabled]); - - const handlePress = () => { - if (disabled) return; - if (open) { - setOpen(false); - return; - } - triggerRef.current?.measure( - ( - _x: number, - _y: number, - width: number, - height: number, - pageX: number, - pageY: number - ) => { - const spaceBelow = Dimensions.get('window').height - (pageY + height); - const y = - spaceBelow >= DROPDOWN_MAX_HEIGHT - ? pageY + height + 2 - : pageY - Math.min(DROPDOWN_MAX_HEIGHT, models.length * 42) - 2; - setDropdownLayout({ x: pageX, y, width }); - setOpen(true); - } - ); - }; - - return ( - - - {label && {label}} - {selected?.label ?? '—'} - {open ? '▲' : '▼'} - - - setOpen(false)} - > - setOpen(false)}> - - - {models.map((item) => { - const isSelected = item.value === selectedModel; - return ( - { - onSelect(item.value); - setOpen(false); - }} - > - - {item.label} - - - ); - })} - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - marginHorizontal: 12, - marginVertical: 4, - alignSelf: 'stretch', - }, - trigger: { - flexDirection: 'row', - alignItems: 'center', - borderWidth: 1, - borderColor: '#C1C6E5', - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 10, - backgroundColor: '#f5f5f5', - }, - triggerDisabled: { - opacity: 0.4, - }, - label: { - fontSize: 12, - color: '#888', - marginRight: 6, - }, - triggerText: { - flex: 1, - fontSize: 14, - color: '#001A72', - fontWeight: '500', - }, - chevron: { - fontSize: 10, - color: '#888', - marginLeft: 6, - }, - dropdown: { - position: 'absolute', - borderWidth: 1, - borderColor: '#C1C6E5', - borderRadius: 8, - backgroundColor: '#fff', - maxHeight: DROPDOWN_MAX_HEIGHT, - elevation: 8, - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.15, - shadowRadius: 4, - }, - option: { - paddingHorizontal: 12, - paddingVertical: 10, - borderBottomWidth: 1, - borderBottomColor: '#f0f0f0', - }, - optionSelected: { - backgroundColor: '#e8ecf8', - }, - optionText: { - fontSize: 14, - color: '#333', - }, - optionTextSelected: { - color: '#001A72', - fontWeight: '600', - }, -}); diff --git a/apps/speech/components/VerboseTranscription.tsx b/apps/speech/components/VerboseTranscription.tsx deleted file mode 100644 index 1093b2bd1c..0000000000 --- a/apps/speech/components/VerboseTranscription.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; -import { TranscriptionResult } from 'react-native-executorch'; - -export const VerboseTranscription = ({ - data, -}: { - data: TranscriptionResult; -}) => { - if (!data) return null; - - const hasSegments = Array.isArray(data.segments) && data.segments.length > 0; - - const hasLanguage = - !!data.language && data.language !== 'N/A' && data.language.trim() !== ''; - - const hasDuration = typeof data.duration === 'number' && data.duration > 0; - - const hasMetadata = hasLanguage || hasDuration; - - return ( - - - Full Text: - {data.text || ''} - - {hasMetadata && ( - - {hasLanguage && ( - Language: {data.language} - )} - {hasDuration && ( - - Duration: {data.duration?.toFixed(2)}s - - )} - - )} - - - {hasSegments && ( - <> - - Segments ({data.segments?.length}) - - - {data.segments?.map((seg, index) => ( - - - - {seg.start.toFixed(2)}s - {seg.end.toFixed(2)}s - - ID: {index} - - - "{seg.text}" - - {seg.words && seg.words.length > 0 && ( - - Word Timestamps: - - {seg.words.map((w, wIdx) => ( - - {w.word.trim()} - - {w.start.toFixed(2)}s - - - ))} - - - )} - - - - Avg LogProb - - {data.task === 'transcribe' - ? seg.avgLogprob?.toFixed(4) - : 'N/A'} - - - - Temp - - {data.task === 'transcribe' - ? seg.temperature?.toFixed(2) - : 'N/A'} - - - - {/*eslint-disable-next-line @cspell/spellchecker*/} - Compr. - - {data.task === 'transcribe' - ? seg.compressionRatio?.toFixed(2) - : 'N/A'} - - - - - ))} - - )} - - ); -}; - -const styles = StyleSheet.create({ - container: { - padding: 4, - }, - metaContainer: { - marginBottom: 16, - padding: 12, - backgroundColor: '#f0f2f5', - borderRadius: 8, - }, - label: { - fontWeight: 'bold', - color: '#0f186e', - marginBottom: 4, - }, - text: { - fontSize: 16, - color: '#333', - marginBottom: 8, - }, - row: { - flexDirection: 'row', - gap: 10, - marginTop: 8, - }, - metaItem: { - fontSize: 12, - color: '#666', - backgroundColor: '#e1e4e8', - paddingHorizontal: 8, - paddingVertical: 2, - borderRadius: 4, - overflow: 'hidden', - }, - sectionHeader: { - fontSize: 18, - fontWeight: 'bold', - color: '#0f186e', - marginBottom: 8, - marginTop: 8, - }, - segmentCard: { - backgroundColor: '#fff', - borderRadius: 8, - borderWidth: 1, - borderColor: '#e1e4e8', - marginBottom: 12, - padding: 12, - shadowColor: '#000', - shadowOffset: { width: 0, height: 1 }, - shadowOpacity: 0.1, - shadowRadius: 2, - elevation: 2, - }, - segmentHeader: { - flexDirection: 'row', - justifyContent: 'space-between', - marginBottom: 8, - }, - timeBadge: { - fontSize: 12, - fontWeight: 'bold', - color: '#fff', - backgroundColor: '#0f186e', - paddingHorizontal: 8, - paddingVertical: 2, - borderRadius: 12, - overflow: 'hidden', - }, - segmentId: { - fontSize: 12, - color: '#888', - }, - segmentText: { - fontSize: 15, - fontStyle: 'italic', - color: '#333', - marginBottom: 12, - }, - statsGrid: { - flexDirection: 'row', - flexWrap: 'wrap', - gap: 8, - borderTopWidth: 1, - borderTopColor: '#f0f0f0', - paddingTop: 8, - }, - statItem: { - flex: 1, - minWidth: '45%', - flexDirection: 'row', - justifyContent: 'space-between', - }, - statLabel: { - fontSize: 11, - color: '#888', - }, - statValue: { - fontSize: 11, - fontWeight: '600', - color: '#444', - }, - wordsContainer: { - marginVertical: 8, - backgroundColor: '#f8f9fa', - padding: 8, - borderRadius: 6, - }, - wordsGrid: { - flexDirection: 'row', - flexWrap: 'wrap', - gap: 6, - marginTop: 4, - }, - wordChip: { - backgroundColor: '#ffffff', - borderWidth: 1, - borderColor: '#e1e4e8', - borderRadius: 4, - paddingHorizontal: 6, - paddingVertical: 2, - alignItems: 'center', - }, - wordText: { - fontSize: 12, - color: '#333', - }, - wordTime: { - fontSize: 9, - color: '#888', - marginTop: 1, - }, -}); diff --git a/apps/speech/declarations.d.ts b/apps/speech/declarations.d.ts deleted file mode 100644 index 85e178f497..0000000000 --- a/apps/speech/declarations.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module '*.svg' { - import { SvgProps } from 'react-native-svg'; - const content: React.FV; - export default content; -} diff --git a/apps/speech/index.ts b/apps/speech/index.ts deleted file mode 100644 index 1d6e981ef6..0000000000 --- a/apps/speech/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { registerRootComponent } from 'expo'; - -import App from './App'; - -// registerRootComponent calls AppRegistry.registerComponent('main', () => App); -// It also ensures that whether you load the app in Expo Go or in a native build, -// the environment is set up appropriately -registerRootComponent(App); diff --git a/apps/speech/metro.config.js b/apps/speech/metro.config.js deleted file mode 100644 index f58bbc2131..0000000000 --- a/apps/speech/metro.config.js +++ /dev/null @@ -1,19 +0,0 @@ -const { getDefaultConfig } = require('expo/metro-config'); - -const config = getDefaultConfig(__dirname); - -const { transformer, resolver } = config; - -config.transformer = { - ...transformer, - babelTransformerPath: require.resolve('react-native-svg-transformer/expo'), -}; -config.resolver = { - ...resolver, - assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'), - sourceExts: [...resolver.sourceExts, 'svg'], -}; - -config.resolver.assetExts.push('pte'); - -module.exports = config; diff --git a/apps/speech/package.json b/apps/speech/package.json deleted file mode 100644 index 9a06a3e3e4..0000000000 --- a/apps/speech/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "speech", - "version": "1.0.0", - "main": "index.ts", - "scripts": { - "start": "expo start", - "android": "expo run:android", - "ios": "expo run:ios", - "web": "expo start --web", - "typecheck": "tsc", - "lint": "eslint . --ext .ts,.tsx --fix" - }, - "dependencies": { - "@expo/vector-icons": "^15.0.2", - "@react-native/metro-config": "^0.85.3", - "buffer": "^6.0.3", - "expo": "~56.0.9", - "expo-build-properties": "~56.0.17", - "expo-font": "~56.0.5", - "expo-status-bar": "~56.0.4", - "metro-config": "^0.84.0", - "react": "19.2.3", - "react-native": "0.85.3", - "react-native-audio-api": "0.12.2", - "react-native-device-info": "^15.0.2", - "react-native-executorch": "workspace:*", - "react-native-executorch-expo-resource-fetcher": "workspace:*", - "react-native-reanimated": "4.3.1", - "react-native-safe-area-context": "~5.7.0", - "react-native-svg": "15.15.4", - "react-native-svg-transformer": "^1.5.3", - "react-native-worklets": "0.8.3" - }, - "devDependencies": { - "@babel/core": "^7.29.0", - "@types/react": "~19.2.14", - "@types/react-refresh": "^0", - "babel-preset-expo": "~56.0.14", - "react-refresh": "^0.18.0" - }, - "private": true -} diff --git a/apps/speech/screens/Quiz.tsx b/apps/speech/screens/Quiz.tsx deleted file mode 100644 index ffd574d96d..0000000000 --- a/apps/speech/screens/Quiz.tsx +++ /dev/null @@ -1,506 +0,0 @@ -import React, { useEffect, useRef, useState, useCallback } from 'react'; -import { - Text, - View, - StyleSheet, - TouchableOpacity, - Platform, - ScrollView, - KeyboardAvoidingView, -} from 'react-native'; -import Animated, { - useSharedValue, - useAnimatedStyle, - withTiming, - withSequence, - withDelay, - runOnJS, -} from 'react-native-reanimated'; -import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; -import { models, useTextToSpeech } from 'react-native-executorch'; -import { - AudioManager, - AudioContext, - AudioBuffer, -} from 'react-native-audio-api'; -import FontAwesome from '@expo/vector-icons/FontAwesome'; -import SWMIcon from '../assets/swm_icon.svg'; -import { QUESTIONS } from '../assets/quiz-data'; - -// Shuffle helper -function shuffleArray(array: T[]): T[] { - const arr = array.slice(); - for (let i = arr.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [arr[i], arr[j]] = [arr[j], arr[i]]; - } - return arr; -} - -// --- Audio Helper --- -const createAudioBufferFromVector = ( - audioVector: Float32Array, - audioContext: AudioContext | null = null, - sampleRate: number = 24000 -): AudioBuffer => { - if (audioContext == null) audioContext = new AudioContext({ sampleRate }); - const audioBuffer = audioContext.createBuffer( - 1, - audioVector.length, - sampleRate - ); - const channelData = audioBuffer.getChannelData(0); - channelData.set(audioVector); - return audioBuffer; -}; - -export const Quiz = ({ onBack }: { onBack: () => void }) => { - // --- Hooks & State --- - const model = useTextToSpeech(models.text_to_speech.kokoro.en_us.santa()); - - const [shuffledQuestions] = useState(() => shuffleArray(QUESTIONS)); - const [currentIndex, setCurrentIndex] = useState(0); - const [selectedAnswer, setSelectedAnswer] = useState(null); - const [isAnswerCorrect, setIsAnswerCorrect] = useState(null); - const [showNext, setShowNext] = useState(false); - const [isSpeaking, setIsSpeaking] = useState(false); - const fadeAnim = useSharedValue(1); - const feedbackAnim = useSharedValue(0); - const nextButtonAnim = useSharedValue(0); - const buttonsInactiveAnim = useSharedValue(1); - - const audioContextRef = useRef(null); - const currentSourceRef = useRef(null); - const isTransitioningRef = useRef(false); - const autoSpeakRef = useRef(true); - - // Animated Styles - const containerStyle = useAnimatedStyle(() => ({ - opacity: fadeAnim.value, - })); - - const feedbackStyle = useAnimatedStyle(() => ({ - opacity: feedbackAnim.value, - })); - - const nextButtonStyle = useAnimatedStyle(() => ({ - opacity: nextButtonAnim.value * buttonsInactiveAnim.value, - transform: [ - { - translateY: (1 - nextButtonAnim.value) * 12, - }, - ], - })); - - // --- Audio Setup --- - useEffect(() => { - AudioManager.setAudioSessionOptions({ - iosCategory: 'playAndRecord', - iosMode: 'spokenAudio', - iosOptions: ['defaultToSpeaker'], - }); - - audioContextRef.current = new AudioContext({ sampleRate: 24000 }); - audioContextRef.current.suspend(); - - return () => { - audioContextRef.current?.close(); - audioContextRef.current = null; - }; - }, []); - - // --- TTS Function --- - const speak = useCallback( - async (text: string) => { - if (!text.trim() || !model.isReady) { - setIsSpeaking(false); - return; - } - - // Stop previous audio if any - if (currentSourceRef.current) { - try { - currentSourceRef.current.stop(); - } catch (e) {} - } - - setIsSpeaking(true); - try { - const audioContext = audioContextRef.current; - if (!audioContext) return; - if (audioContext.state === 'suspended') await audioContext.resume(); - - const onNext = async (audioVec: Float32Array) => { - return new Promise((resolve) => { - const audioBuffer = createAudioBufferFromVector( - audioVec, - audioContext, - 24000 - ); - const source = audioContext.createBufferSource(); - source.buffer = audioBuffer; - source.connect(audioContext.destination); - currentSourceRef.current = source; - source.onEnded = () => resolve(); - source.start(); - }); - }; - - await model.stream({ text, speed: 0.9, onNext, onEnd: async () => {} }); - } catch (e) { - console.error(e); - } finally { - setIsSpeaking(false); - } - }, - [model] - ); - - // --- Game Logic --- - const currentQ = shuffledQuestions[currentIndex]; - - // Speak question on load - useEffect(() => { - if (!model.isReady) return; - if (!autoSpeakRef.current) { - autoSpeakRef.current = true; - return; - } - setIsSpeaking(true); - const t = setTimeout(() => speak(currentQ.q), 500); - return () => clearTimeout(t); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentIndex, model.isReady]); - - const handleAnswer = async (index: number) => { - if (selectedAnswer !== null || isSpeaking) return; // Prevent double taps or clicks while reading - - setSelectedAnswer(index); - const correct = index === currentQ.c; - setIsAnswerCorrect(correct); - - // 1. Visual Feedback Animation (1s) - feedbackAnim.value = withSequence( - withTiming(1, { duration: 200 }), - withDelay(1000, withTiming(0, { duration: 200 })) - ); - - // 2. Audio Feedback - if (correct) { - await speak('Correct!'); - } else { - // Play "Incorrect" and explanation as one string - await speak(`Incorrect. ${currentQ.e}`); - } - - // 3. Show Next Button - setShowNext(true); - }; - - const updateQuestionState = (nextIdx: number) => { - setSelectedAnswer(null); - setIsAnswerCorrect(null); - setCurrentIndex(nextIdx); - - setTimeout(() => { - isTransitioningRef.current = false; - fadeAnim.value = withTiming(1, { duration: 500 }); - }, 500); - }; - - const handleNext = () => { - const nextIdx = (currentIndex + 1) % shuffledQuestions.length; - buttonsInactiveAnim.value = 1; // Ensure buttons are active for next round - autoSpeakRef.current = false; - speak(shuffledQuestions[nextIdx].q); - - isTransitioningRef.current = true; - setShowNext(false); - - fadeAnim.value = withTiming(0, { duration: 500 }, (finished) => { - if (finished) { - runOnJS(updateQuestionState)(nextIdx); - } - }); - }; - - const handleLearnMore = async () => { - if (isSpeaking) return; - - buttonsInactiveAnim.value = withTiming(0.5, { duration: 800 }); - - // Play the context for the current question - await speak(currentQ.context); - - buttonsInactiveAnim.value = withTiming(1, { duration: 800 }); - }; - - const getButtonColor = (index: number) => { - if (selectedAnswer === null) return styles.optionButton; - - if (index === currentQ.c) return styles.correctButton; // Highlight correct answer always if answered - if (index === selectedAnswer && !isAnswerCorrect) return styles.wrongButton; // Highlight mistake - - return styles.disabledOption; - }; - - // Animate next button appearance - useEffect(() => { - nextButtonAnim.value = withTiming(showNext ? 1 : 0, { - duration: 450, - }); - }, [showNext, nextButtonAnim]); - - return ( - - - - - - - - Text to Speech - Quiz - - - {!model.isReady ? ( - - - Loading Model: {Math.round(model.downloadProgress * 100)}% - - - ) : ( - - - - - - Question {currentIndex + 1} - - {currentQ.q} - - - - {currentQ.a.map((opt, idx) => ( - handleAnswer(idx)} - disabled={selectedAnswer !== null || isSpeaking} - > - {opt} - - ))} - - - {/* Feedback Animation Overlay Text */} - {selectedAnswer !== null && ( - - - {isAnswerCorrect ? 'Correct!' : 'Incorrect'} - - - )} - - - - {showNext && ( - - - Next Question - - - - - - Learn More - - - - - )} - - )} - - - ); -}; - -// Reuse stylistic approach from TextToSpeechScreen -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: 'white', - }, - centerContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - loadingText: { - fontSize: 18, - color: '#0f186e', - }, - header: { - flexDirection: 'row', - alignItems: 'center', - padding: 16, - borderBottomWidth: 1, - borderBottomColor: '#eee', - position: 'relative', - }, - backButton: { - paddingRight: 15, - }, - headerText: { - fontSize: 20, - fontWeight: 'bold', - color: '#0f186e', - marginLeft: 10, - }, - scrollContent: { - padding: 20, - paddingBottom: 160, // Space for bottom container - flexGrow: 1, // Ensures content scales to screen height - justifyContent: 'center', - }, - quizContainer: { - width: '100%', - }, - questionCard: { - backgroundColor: '#0f186e', - borderRadius: 16, - padding: 24, - marginBottom: 24, - minHeight: 150, - justifyContent: 'center', - }, - questionIndex: { - color: 'rgba(255,255,255,0.7)', - fontSize: 14, - marginBottom: 8, - textTransform: 'uppercase', - fontWeight: 'bold', - }, - questionText: { - color: 'white', - fontSize: 22, - fontWeight: '600', - lineHeight: 30, - }, - optionsContainer: { - gap: 12, - }, - baseOption: { - padding: 16, - borderRadius: 12, - borderWidth: 2, - flexDirection: 'row', - alignItems: 'center', - }, - // States of options - optionButton: { - backgroundColor: 'white', - borderColor: '#e0e0e0', - }, - correctButton: { - backgroundColor: '#E8F5E9', - borderColor: '#4CAF50', - }, - wrongButton: { - backgroundColor: '#FFEBEE', - borderColor: '#F44336', - }, - disabledOption: { - backgroundColor: '#f5f5f5', - borderColor: '#eee', - opacity: 0.6, - }, - - optionText: { - fontSize: 18, - color: '#333', - fontWeight: '500', - }, - feedbackContainer: { - alignItems: 'center', - marginVertical: 10, - }, - feedbackText: { - fontSize: 24, - fontWeight: 'bold', - marginVertical: 10, - }, - feedbackTextCorrect: { - color: '#4CAF50', - }, - feedbackTextIncorrect: { - color: '#F44336', - }, - nextButton: { - // Removed marginTop to rely on container padding - backgroundColor: '#0f186e', - paddingVertical: 16, - paddingHorizontal: 32, - borderRadius: 30, - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - gap: 10, - alignSelf: 'center', - width: '100%', - }, - learnMoreButton: { - marginTop: 0, - backgroundColor: 'white', - borderWidth: 2, - borderColor: '#0f186e', - }, - nextButtonText: { - color: 'white', - fontSize: 18, - fontWeight: 'bold', - }, - learnMoreButtonText: { - color: '#0f186e', - }, - bottomContainer: { - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - padding: 20, - gap: 12, - alignItems: 'center', - backgroundColor: 'rgba(255,255,255,0.95)', - }, - flex1: { - flex: 1, - }, -}); diff --git a/apps/speech/screens/SpeechToTextScreen.tsx b/apps/speech/screens/SpeechToTextScreen.tsx deleted file mode 100644 index 909c2da57f..0000000000 --- a/apps/speech/screens/SpeechToTextScreen.tsx +++ /dev/null @@ -1,610 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { - Text, - View, - StyleSheet, - TouchableOpacity, - ScrollView, - TextInput, - KeyboardAvoidingView, - Platform, - Switch, - Keyboard, -} from 'react-native'; -import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; -import { - models, - useSpeechToText, - TranscriptionResult, - SpeechToTextProps, -} from 'react-native-executorch'; -import { ModelPicker, ModelOption } from '../components/ModelPicker'; -const speechToText = models.speech_to_text; -const vad = models.vad; - -const isSimulator = DeviceInfo.isEmulatorSync(); -const backend = Platform.OS === 'ios' && !isSimulator ? 'coreml' : 'xnnpack'; - -type STTModelSources = SpeechToTextProps['model']; - -const MODELS: ModelOption[] = [ - { - label: 'Whisper Tiny EN', - value: speechToText.whisper_tiny_en({ backend }), - }, - { - label: 'Whisper Base EN', - value: speechToText.whisper_base_en({ backend }), - }, - { - label: 'Whisper Small EN', - value: speechToText.whisper_small_en({ backend }), - }, -]; -import FontAwesome from '@expo/vector-icons/FontAwesome'; -import { - AudioManager, - AudioRecorder, - AudioContext, -} from 'react-native-audio-api'; -import * as FileSystem from 'expo-file-system/legacy'; -import SWMIcon from '../assets/swm_icon.svg'; -import DeviceInfo from 'react-native-device-info'; - -import { VerboseTranscription } from '../components/VerboseTranscription'; -import ErrorBanner from '../components/ErrorBanner'; - -export const SpeechToTextScreen = ({ onBack }: { onBack: () => void }) => { - const [selectedModel, setSelectedModel] = useState( - Platform.OS === 'ios' && !isSimulator - ? speechToText.whisper_base_en({ backend }) - : speechToText.whisper_tiny_en() - ); - - const model = useSpeechToText({ - model: selectedModel, - vad: vad.fsmn_vad(), - }); - - const [transcription, setTranscription] = - useState(null); - const [transcriptionTime, setTranscriptionTime] = useState( - null - ); - - const [liveResult, setLiveResult] = useState<{ - fullText: string; - segments: any[]; - } | null>(null); - - const [enableTimestamps, setEnableTimestamps] = useState(false); - const [useVAD, setUseVAD] = useState(true); - const [error, setError] = useState(null); - const [audioURL, setAudioURL] = useState(''); - const [hasMicPermission, setHasMicPermission] = useState(false); - - const isRecordingRef = useRef(false); - const [liveTranscribing, setLiveTranscribing] = useState(false); - const scrollViewRef = useRef(null); - - const recorder = useRef(new AudioRecorder()); - - useEffect(() => { - AudioManager.setAudioSessionOptions({ - iosCategory: 'playAndRecord', - iosMode: 'spokenAudio', - iosOptions: ['allowBluetoothHFP', 'defaultToSpeaker'], - }); - const checkPerms = async () => { - const status = await AudioManager.requestRecordingPermissions(); - setHasMicPermission(status === 'Granted'); - }; - checkPerms(); - }, []); - - async function getAudioFile(sourceUri: string) { - const destination = FileSystem.cacheDirectory + 'audio_file.wav'; - - if (sourceUri.startsWith('http')) { - const { uri } = await FileSystem.downloadAsync(sourceUri, destination); - return uri; - } else { - await FileSystem.copyAsync({ - from: sourceUri, - to: destination, - }); - return destination; - } - } - - const handleTranscribeFromURL = async () => { - if (!audioURL.trim() || model.isGenerating) { - if (!audioURL.trim()) { - console.warn('Please provide a valid audio file URL'); - } - return; - } - - Keyboard.dismiss(); - - // Reset previous states - setTranscription(null); - setLiveResult(null); - - try { - const uri = await getAudioFile(audioURL); - const audioContext = new AudioContext({ sampleRate: 16000 }); - const decodedAudioData = await audioContext.decodeAudioData(uri); - const audioBuffer = decodedAudioData.getChannelData(0); - const start = Date.now(); - const result = await model.transcribe(audioBuffer, { - verbose: enableTimestamps, - }); - setTranscriptionTime(Date.now() - start); - setTranscription(result); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - return; - } - }; - - const handleStartTranscribeFromMicrophone = async () => { - if (!hasMicPermission || model.isGenerating || liveTranscribing) { - if (!hasMicPermission) { - setError('Microphone permission denied. Please enable it in Settings.'); - } - return; - } - - isRecordingRef.current = true; - setLiveTranscribing(true); - - setTranscription(null); - setLiveResult({ fullText: '', segments: [] }); - - const sampleRate = 16000; - - recorder.current.onAudioReady( - { - sampleRate, - bufferLength: 0.1 * sampleRate, // 100 ms - channelCount: 1, - }, - ({ buffer }) => { - model.streamInsert(buffer.getChannelData(0)); - } - ); - - try { - const success = await AudioManager.setAudioSessionActivity(true); - if (!success) { - setError('Cannot start audio session correctly'); - } - const result = recorder.current.start(); - if (result.status === 'error') { - setError(`Recording problems: ${result.message}`); - } - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - isRecordingRef.current = false; - setLiveTranscribing(false); - return; - } - - let accumulatedText = ''; - let accumulatedSegments: any[] = []; - - try { - const streamIter = model.stream({ - verbose: enableTimestamps, - timeout: 200, - useVAD: useVAD, - vadDetectionMargin: 1200, - }); - - for await (const { committed, nonCommitted } of streamIter) { - if (!isRecordingRef.current) break; - - if (committed.text) { - accumulatedText += committed.text; - } - if (committed.segments) { - accumulatedSegments = [...accumulatedSegments, ...committed.segments]; - } - - const currentDisplay = { - fullText: accumulatedText + nonCommitted.text, - segments: [...accumulatedSegments, ...(nonCommitted.segments || [])], - }; - - setLiveResult(currentDisplay); - } - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } finally { - setLiveTranscribing(false); - } - }; - - const handleStopTranscribeFromMicrophone = () => { - isRecordingRef.current = false; - - recorder.current.stop(); - model.streamStop(); - console.log('Live transcription stopped'); - setLiveTranscribing(false); - - if (liveResult) { - setTranscription({ - text: liveResult.fullText, - segments: liveResult.segments, - language: 'en', - duration: 0, - }); - setLiveResult(null); - } - }; - - const getModelStatus = () => { - if (model.isGenerating) return 'Transcribing...'; - if (model.isReady) return 'Ready to transcribe'; - return `Loading model: ${(100 * model.downloadProgress).toFixed(2)}%`; - }; - - useEffect(() => { - if (model.error) setError(String(model.error)); - }, [model.error]); - - const readyToTranscribe = !model.isGenerating && model.isReady; - const recordingButtonDisabled = isSimulator || !readyToTranscribe; - - const getDisplayData = (): TranscriptionResult | null => { - if (liveTranscribing && liveResult) { - return { - text: liveResult.fullText, - segments: liveResult.segments, - language: 'en', - duration: 0, - }; - } - return transcription; - }; - - const displayData = getDisplayData(); - - return ( - - - - - - - - - React Native ExecuTorch - Speech to Text - - - - Status: {getModelStatus()} - {transcriptionTime !== null && ( - - Transcription: {transcriptionTime} ms - - )} - - setError(null)} /> - - { - setSelectedModel(m); - setTranscription(null); - setLiveResult(null); - }} - /> - - - Enable Timestamps (Verbose) - { - setEnableTimestamps(val); - setTranscription(null); - setLiveResult(null); - }} - trackColor={{ false: '#767577', true: '#0f186e' }} - thumbColor={enableTimestamps ? '#fff' : '#f4f3f4'} - disabled={model.isGenerating} - /> - - - - Transcription - - scrollViewRef.current?.scrollToEnd({ animated: true }) - } - > - {displayData ? ( - - ) : ( - - {liveTranscribing - ? 'Listening...' - : 'No transcription yet...'} - - )} - - - - - - - - Start - - - - {liveTranscribing ? ( - - - Stop Live Transcription - - ) : ( - - - - - {isSimulator ? 'No Mic' : 'Start Live'} - - - - setUseVAD(!useVAD)} - activeOpacity={0.7} - accessibilityRole="switch" - accessibilityState={{ checked: useVAD }} - accessibilityLabel={`Voice Activity Detection ${useVAD ? 'on' : 'off'}`} - style={[ - styles.vadButton, - useVAD ? styles.vadActive : styles.vadInactive, - recordingButtonDisabled && styles.disabled, - ]} - > - - - - VAD - - - {useVAD ? 'ON' : 'OFF'} - - - - - )} - - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - backgroundColor: 'white', - paddingHorizontal: 16, - }, - keyboardAvoidingView: { - flex: 1, - width: '100%', - }, - header: { - alignItems: 'center', - position: 'relative', - width: '100%', - }, - backButton: { - position: 'absolute', - left: 0, - top: 10, - padding: 10, - zIndex: 1, - }, - headerText: { - fontSize: 22, - fontWeight: 'bold', - color: '#0f186e', - }, - statusContainer: { - marginTop: 12, - alignItems: 'center', - }, - statsText: { - fontSize: 13, - color: '#334155', - fontWeight: '500', - marginTop: 4, - }, - toggleContainer: { - flexDirection: 'row', - alignItems: 'center', - marginTop: 10, - marginBottom: 5, - }, - toggleLabel: { - fontSize: 16, - marginRight: 10, - color: '#0f186e', - }, - transcriptionContainer: { - flex: 1, - width: '100%', - marginVertical: 12, - }, - transcriptionLabel: { - marginLeft: 12, - marginBottom: 4, - color: '#0f186e', - }, - transcriptionScrollContainer: { - borderRadius: 12, - borderWidth: 1, - borderColor: '#0f186e', - padding: 12, - maxHeight: 400, - }, - placeholderText: { - color: '#aaa', - fontStyle: 'italic', - }, - inputContainer: { - marginBottom: 12, - }, - urlTranscriptionContainer: { - width: '100%', - flexDirection: 'row', - }, - urlTranscriptionInput: { - flex: 1, - padding: 12, - borderTopLeftRadius: 12, - borderBottomLeftRadius: 12, - borderWidth: 1, - borderColor: '#0f186e', - borderRightWidth: 0, - color: '#0f186e', - }, - urlTranscriptionButton: { - backgroundColor: '#0f186e', - justifyContent: 'center', - alignItems: 'center', - padding: 12, - borderTopRightRadius: 12, - borderBottomRightRadius: 12, - }, - buttonText: { - color: 'white', - fontWeight: '600', - letterSpacing: -0.5, - fontSize: 16, - }, - liveTranscriptionButton: { - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - padding: 12, - borderRadius: 12, - marginTop: 12, - gap: 8, - }, - backgroundRed: { - backgroundColor: 'red', - }, - backgroundBlue: { - backgroundColor: '#0f186e', - }, - buttonRow: { - flexDirection: 'row', - gap: 8, - marginTop: 12, - }, - flex1: { - flex: 1, - marginTop: 0, - }, - vadButton: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - paddingHorizontal: 14, - borderRadius: 12, - gap: 10, - }, - vadActive: { - backgroundColor: '#0f186e', - }, - vadInactive: { - backgroundColor: '#f1f5f9', - }, - vadTextContainer: { - alignItems: 'flex-start', - }, - vadButtonLabel: { - fontWeight: '800', - fontSize: 13, - letterSpacing: 0.5, - }, - vadButtonLabelActive: { - color: 'white', - }, - vadButtonLabelInactive: { - color: '#64748b', - }, - vadButtonState: { - fontWeight: '700', - fontSize: 10, - letterSpacing: 1, - }, - vadButtonStateActive: { - color: '#bbf7d0', - }, - vadButtonStateInactive: { - color: '#94a3b8', - }, - disabled: { - opacity: 0.5, - }, -}); diff --git a/apps/speech/screens/TextToSpeechLLMScreen.tsx b/apps/speech/screens/TextToSpeechLLMScreen.tsx deleted file mode 100644 index b90bcad577..0000000000 --- a/apps/speech/screens/TextToSpeechLLMScreen.tsx +++ /dev/null @@ -1,318 +0,0 @@ -import React, { useEffect, useState, useRef } from 'react'; -import { - View, - Text, - StyleSheet, - TouchableOpacity, - ScrollView, -} from 'react-native'; -import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; -import FontAwesome from '@expo/vector-icons/FontAwesome'; -import SWMIcon from '../assets/swm_icon.svg'; -import { models, useLLM, useTextToSpeech } from 'react-native-executorch'; -import { - AudioManager, - AudioContext, - AudioBuffer, - AudioBufferSourceNode, -} from 'react-native-audio-api'; - -interface TextToSpeechLLMProps { - onBack: () => void; -} - -/** - * Converts an audio vector (Float32Array) to an AudioBuffer for playback - * @param audioVector - The generated audio samples from the model - * @param audioContext - The audio context used to create the buffer. - * @param sampleRate - The sample rate (default: 24000 Hz for Kokoro) - * @returns AudioBuffer ready for playback - */ -const createAudioBufferFromVector = ( - audioVector: Float32Array, - audioContext: AudioContext, - sampleRate: number = 24000 -): AudioBuffer => { - const audioBuffer = audioContext.createBuffer( - 1, - audioVector.length, - sampleRate - ); - const channelData = audioBuffer.getChannelData(0); - channelData.set(audioVector); - - return audioBuffer; -}; - -export const TextToSpeechLLMScreen = ({ onBack }: TextToSpeechLLMProps) => { - const [displayText, setDisplayText] = useState(''); - const [isTtsStreaming, setIsTtsStreaming] = useState(false); - const llm = useLLM({ model: models.llm.llama3_2_1b() }); - const tts = useTextToSpeech(models.text_to_speech.kokoro.en_us.heart()); - - const processedLengthRef = useRef(0); - const audioContextRef = useRef(null); - const sourceRef = useRef(null); - - useEffect(() => { - AudioManager.setAudioSessionOptions({ - iosCategory: 'playAndRecord', - iosMode: 'spokenAudio', - iosOptions: ['defaultToSpeaker'], - }); - - audioContextRef.current = new AudioContext({ sampleRate: 24000 }); - audioContextRef.current.suspend(); - - return () => { - audioContextRef.current?.close(); - audioContextRef.current = null; - }; - }, []); - - // Update displayText gradually as response gets generated and insert new text chunks into TTS stream - useEffect(() => { - if (llm.response && tts.isReady) { - setDisplayText(llm.response); - - const previousLength = processedLengthRef.current; - if (llm.response.length > previousLength && isTtsStreaming) { - const newChunk = llm.response.slice(previousLength); - tts.streamInsert(newChunk); - processedLengthRef.current = llm.response.length; - } - } else { - processedLengthRef.current = 0; - } - }, [llm.response, tts, isTtsStreaming]); - - const handleGenerate = async () => { - setDisplayText(''); - processedLengthRef.current = 0; - setIsTtsStreaming(true); - - const startTTS = async () => { - try { - const audioContext = audioContextRef.current; - if (!audioContext) return; - - if (audioContext.state === 'suspended') { - await audioContext.resume(); - } - - const onNext = async (audioVec: Float32Array) => { - return new Promise((resolve) => { - const audioBuffer = createAudioBufferFromVector( - audioVec, - audioContext, - 24000 - ); - - const source = (sourceRef.current = - audioContext.createBufferSource()); - source.buffer = audioBuffer; - source.connect(audioContext.destination); - - source.onEnded = () => resolve(); - - source.start(); - }); - }; - - await tts.stream({ - speed: 0.9, - stopAutomatically: false, - onNext, - }); - } catch (e) { - console.error('TTS streaming error:', e); - } finally { - setIsTtsStreaming(false); - } - }; - - const ttsPromise = startTTS(); - - try { - await llm.sendMessage( - 'Generate a short story about a robot learning to paint. The story should be around 200 words long.' - ); - } catch (e) { - console.error('Generation failed:', e); - } finally { - // LLM finished — partition any trailing un-terminated tail so it gets - // synthesized before the stream closes. - tts.streamFlush(); - tts.streamStop(false); - await ttsPromise; - - if ( - audioContextRef.current && - audioContextRef.current.state === 'running' - ) { - await audioContextRef.current.suspend(); - } - } - }; - - const handleStop = () => { - llm.interrupt(); - tts.streamStop(true); - if (sourceRef.current) { - try { - sourceRef.current.stop(); - } catch (e) { - // Source might have already stopped or disconnected - } - } - }; - - const isProcessing = llm.isGenerating || isTtsStreaming; - const isModelsReady = llm.isReady && tts.isReady; - - const getModelStatus = () => { - if (llm.error) return `LLM Error: ${llm.error.message}`; - if (tts.error) return `TTS Error: ${tts.error.message}`; - if (!llm.isReady) - return `Loading LLM: ${(100 * llm.downloadProgress).toFixed(2)}%`; - if (!tts.isReady) - return `Loading TTS: ${(100 * tts.downloadProgress).toFixed(2)}%`; - if (isProcessing) return 'Generating/Streaming...'; - return 'Ready'; - }; - - return ( - - - - - - - - React Native ExecuTorch - LLM to Speech Demo - - - - Status: {getModelStatus()} - - - - Generated Story - - - - {displayText || - (isModelsReady - ? 'Press the button to generate a story and hear it spoken aloud.' - : 'Please wait for models to load...')} - - - - - - - {isProcessing ? ( - - - Stop Generation - - ) : ( - - - Generate & Stream Speech - - )} - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - backgroundColor: 'white', - paddingHorizontal: 16, - }, - header: { - alignItems: 'center', - position: 'relative', - width: '100%', - }, - backButton: { - position: 'absolute', - left: 0, - top: 10, - padding: 10, - zIndex: 1, - }, - headerText: { - fontSize: 22, - fontWeight: 'bold', - color: '#0f186e', - }, - statusContainer: { - marginTop: 12, - alignItems: 'center', - }, - contentContainer: { - width: '100%', - marginTop: 24, - flex: 1, - marginBottom: 24, - }, - label: { - marginLeft: 12, - marginBottom: 4, - color: '#0f186e', - fontWeight: '600', - }, - responseContainer: { - borderRadius: 12, - borderWidth: 1, - borderColor: '#0f186e', - flex: 1, - }, - responseContent: { - padding: 12, - }, - responseText: { - fontSize: 16, - color: '#333', - lineHeight: 24, - }, - buttonContainer: { - marginBottom: 24, - width: '100%', - }, - actionButton: { - backgroundColor: '#0f186e', - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - padding: 12, - borderRadius: 12, - gap: 8, - }, - stopButton: { - backgroundColor: '#ff4444', - }, - buttonText: { - color: 'white', - fontWeight: '600', - letterSpacing: -0.5, - fontSize: 16, - }, - disabled: { - opacity: 0.5, - }, -}); diff --git a/apps/speech/screens/TextToSpeechScreen.tsx b/apps/speech/screens/TextToSpeechScreen.tsx deleted file mode 100644 index 919076dc35..0000000000 --- a/apps/speech/screens/TextToSpeechScreen.tsx +++ /dev/null @@ -1,328 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { - Text, - View, - StyleSheet, - TouchableOpacity, - TextInput, - KeyboardAvoidingView, - Platform, - Keyboard, -} from 'react-native'; -import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; -import { - models, - useTextToSpeech, - TextToSpeechModelConfig, -} from 'react-native-executorch'; -import { ModelPicker, ModelOption } from '../components/ModelPicker'; - -const tts = models.text_to_speech.kokoro; - -const VOICES: ModelOption[] = [ - { label: '🇺🇸 AF Heart', value: tts.en_us.heart() }, - { label: '🇺🇸 AF River', value: tts.en_us.river() }, - { label: '🇺🇸 AF Sarah', value: tts.en_us.sarah() }, - { label: '🇺🇸 AM Adam', value: tts.en_us.adam() }, - { label: '🇺🇸 AM Michael', value: tts.en_us.michael() }, - { label: '🇺🇸 AM Santa', value: tts.en_us.santa() }, - { label: '🇬🇧 BF Emma', value: tts.en_gb.emma() }, - { label: '🇬🇧 BM Daniel', value: tts.en_gb.daniel() }, - { label: '🇫🇷 FF Siwis', value: tts.fr.siwis() }, - { label: '🇪🇸 EF Dora', value: tts.es.dora() }, - { label: '🇪🇸 EM Alex', value: tts.es.alex() }, - { label: '🇮🇹 IF Sara', value: tts.it.sara() }, - { label: '🇮🇹 IM Nicola', value: tts.it.nicola() }, - { label: '🇵🇹 PF Dora', value: tts.pt.dora() }, - { label: '🇵🇹 PM Santa', value: tts.pt.santa() }, - { label: '🇩🇪 DF Anna', value: tts.de.anna() }, - { label: '🇵🇱 PM Mateusz', value: tts.pl.mateusz() }, - { label: '🇮🇳 HF Alpha', value: tts.hi.alpha() }, - { label: '🇮🇳 HM Omega', value: tts.hi.omega() }, - { label: '🇮🇳 HM Psi', value: tts.hi.psi() }, -]; - -import FontAwesome from '@expo/vector-icons/FontAwesome'; -import { - AudioManager, - AudioContext, - AudioBuffer, - AudioBufferSourceNode, -} from 'react-native-audio-api'; -import SWMIcon from '../assets/swm_icon.svg'; -import ErrorBanner from '../components/ErrorBanner'; - -/** - * Converts an audio vector (Float32Array) to an AudioBuffer for playback - * @param audioVector - The generated audio samples from the model - * @param audioContext - An optional AudioContext to create the buffer in. If not provided, a new one will be created. - * @param sampleRate - The sample rate (default: 24000 Hz for Kokoro) - * @returns AudioBuffer ready for playback - */ -const createAudioBufferFromVector = ( - audioVector: Float32Array, - audioContext: AudioContext | null = null, - sampleRate: number = 24000 -): AudioBuffer => { - if (audioContext == null) audioContext = new AudioContext({ sampleRate }); - - const audioBuffer = audioContext.createBuffer( - 1, - audioVector.length, - sampleRate - ); - const channelData = audioBuffer.getChannelData(0); - channelData.set(audioVector); - - return audioBuffer; -}; - -export const TextToSpeechScreen = ({ onBack }: { onBack: () => void }) => { - const [selectedSpeaker, setSelectedSpeaker] = - useState(tts.en_us.heart()); - - const model = useTextToSpeech(selectedSpeaker); - - const [inputText, setInputText] = useState(''); - const [isPlaying, setIsPlaying] = useState(false); - const [readyToGenerate, setReadyToGenerate] = useState(false); - const [error, setError] = useState(null); - - const audioContextRef = useRef(null); - const gainNodeRef = useRef(null); - const sourceRef = useRef(null); - - useEffect(() => { - AudioManager.setAudioSessionOptions({ - iosCategory: 'playAndRecord', - iosMode: 'spokenAudio', - iosOptions: ['defaultToSpeaker'], - }); - - const context = new AudioContext({ sampleRate: 24000 }); - audioContextRef.current = context; - context.suspend(); - - // Increase the audio volume - const gainNode = context.createGain(); - gainNode.gain.value = 2.0; // Increase volume by 2x - gainNode.connect(context.destination); - gainNodeRef.current = gainNode; - - return () => { - audioContextRef.current?.close(); - audioContextRef.current = null; - gainNodeRef.current = null; - }; - }, []); - - useEffect(() => { - setReadyToGenerate(!model.isGenerating && model.isReady && !isPlaying); - }, [model.isGenerating, model.isReady, isPlaying]); - - const handlePlayAudio = async () => { - if (!inputText.trim()) { - return; - } - - Keyboard.dismiss(); - setIsPlaying(true); - - try { - const audioContext = audioContextRef.current; - if (!audioContext) return; - - if (audioContext.state === 'suspended') { - await audioContext.resume(); - } - - const onNext = async (audioVec: Float32Array) => { - return new Promise((resolve) => { - const audioBuffer = createAudioBufferFromVector( - audioVec, - audioContext, - 24000 - ); - - const source = (sourceRef.current = - audioContext.createBufferSource()); - source.buffer = audioBuffer; - - if (gainNodeRef.current) { - source.connect(gainNodeRef.current); - } else { - source.connect(audioContext.destination); - } - - source.onEnded = () => resolve(); - - source.start(); - }); - }; - - const onEnd = async () => { - setIsPlaying(false); - await audioContext.suspend(); - }; - - await model.stream({ - text: inputText, - speed: 0.9, - phonemize: true, - onNext, - onEnd, - }); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - setIsPlaying(false); - } - }; - - const getModelStatus = () => { - if (model.isGenerating) return 'Generating audio...'; - if (model.isReady) return 'Ready to synthesize'; - return `Loading model: ${(100 * model.downloadProgress).toFixed(2)}%`; - }; - - useEffect(() => { - if (model.error) setError(String(model.error)); - }, [model.error]); - - return ( - - - - - - - - - React Native ExecuTorch - Text to Speech - - - - Status: {getModelStatus()} - - setError(null)} /> - - setSelectedSpeaker(m)} - /> - - - Enter text to synthesize - - - - - - - - {isPlaying ? 'Playing...' : 'Generate & Play'} - - - - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - backgroundColor: 'white', - paddingHorizontal: 16, - }, - keyboardAvoidingView: { - flex: 1, - width: '100%', - }, - header: { - alignItems: 'center', - position: 'relative', - width: '100%', - }, - backButton: { - position: 'absolute', - left: 0, - top: 10, - padding: 10, - zIndex: 1, - }, - headerText: { - fontSize: 22, - fontWeight: 'bold', - color: '#0f186e', - }, - statusContainer: { - marginTop: 12, - alignItems: 'center', - }, - inputContainer: { - width: '100%', - marginTop: 24, - }, - inputLabel: { - marginLeft: 12, - marginBottom: 4, - color: '#0f186e', - fontWeight: '600', - }, - textInput: { - borderRadius: 12, - borderWidth: 1, - borderColor: '#0f186e', - padding: 12, - minHeight: 120, - fontSize: 16, - color: '#0f186e', - }, - buttonContainer: { - marginTop: 24, - }, - playButton: { - backgroundColor: '#0f186e', - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - padding: 12, - borderRadius: 12, - gap: 8, - }, - buttonText: { - color: 'white', - fontWeight: '600', - letterSpacing: -0.5, - fontSize: 16, - }, - disabled: { - opacity: 0.5, - }, -}); diff --git a/apps/speech/screens/VoiceActivityDetectionScreen.tsx b/apps/speech/screens/VoiceActivityDetectionScreen.tsx deleted file mode 100644 index 724ea52500..0000000000 --- a/apps/speech/screens/VoiceActivityDetectionScreen.tsx +++ /dev/null @@ -1,329 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { - Text, - View, - StyleSheet, - TouchableOpacity, - ScrollView, - Platform, -} from 'react-native'; -import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; -import { - models, - useVAD -} from 'react-native-executorch'; -import FontAwesome from '@expo/vector-icons/FontAwesome'; -import { AudioManager, AudioRecorder } from 'react-native-audio-api'; -import SWMIcon from '../assets/swm_icon.svg'; -import DeviceInfo from 'react-native-device-info'; -import ErrorBanner from '../components/ErrorBanner'; - -const isSimulator = DeviceInfo.isEmulatorSync(); - -export const VoiceActivityDetectionScreen = ({ - onBack, -}: { - onBack: () => void; -}) => { - const model = useVAD({ - model: models.vad.fsmn_vad(), - }); - - const [isSpeaking, setIsSpeaking] = useState(false); - const [error, setError] = useState(null); - const [hasMicPermission, setHasMicPermission] = useState(false); - const [isStreaming, setIsStreaming] = useState(false); - - const recorder = useRef(new AudioRecorder()); - const logScrollRef = useRef(null); - const [logs, setLogs] = useState([]); - - const addLog = (msg: string) => { - setLogs((prev) => [...prev, `${new Date().toLocaleTimeString()}: ${msg}`]); - }; - - useEffect(() => { - AudioManager.setAudioSessionOptions({ - iosCategory: 'playAndRecord', - iosMode: 'spokenAudio', - iosOptions: ['allowBluetoothHFP', 'defaultToSpeaker'], - }); - const checkPerms = async () => { - const status = await AudioManager.requestRecordingPermissions(); - setHasMicPermission(status === 'Granted'); - }; - checkPerms(); - }, []); - - const handleStartStreaming = async () => { - if (isStreaming || model.isGenerating || !model.isReady) { - return; - } - - setIsStreaming(true); - if (!hasMicPermission) { - setError('Microphone permission denied. Please enable it in Settings.'); - setIsStreaming(false); - return; - } - - setLogs([]); - addLog('Starting VAD stream...'); - - const sampleRate = 16000; - - recorder.current.onAudioReady( - { - sampleRate, - bufferLength: 0.1 * sampleRate, - channelCount: 1, - }, - ({ buffer }) => { - model.streamInsert(buffer.getChannelData(0)); - } - ); - - try { - const success = await AudioManager.setAudioSessionActivity(true); - if (!success) { - setError('Cannot start audio session correctly'); - } - const result = recorder.current.start(); - if (result.status === 'error') { - setError(`Recording problems: ${result.status}`); - } - - await model.stream({ - onSpeechBegin: () => { - setIsSpeaking(true); - addLog('Speech detected (Begin)'); - }, - onSpeechEnd: () => { - setIsSpeaking(false); - addLog('Silence detected (End)'); - }, - options: { - timeout: 100, - detectionMargin: 300, - }, - }); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - setIsStreaming(false); - } - }; - - const handleStopStreaming = () => { - recorder.current.stop(); - model.streamStop(); - setIsStreaming(false); - setIsSpeaking(false); - addLog('VAD stream stopped'); - }; - - const getModelStatus = () => { - if (isStreaming || model.isGenerating) return 'Processing...'; - if (model.isReady) return 'Ready'; - return `Loading model: ${(100 * model.downloadProgress).toFixed(2)}%`; - }; - - useEffect(() => { - if (model.error) setError(String(model.error)); - }, [model.error]); - - const readyToStream = model.isReady; - const recordingButtonDisabled = - isSimulator || !readyToStream || model.isGenerating; - - return ( - - - - - - - - React Native ExecuTorch - Voice Activity Detection - - - - Status: {getModelStatus()} - - - setError(null)} /> - - - - - {isSpeaking ? 'SPEAKING' : 'SILENT'} - - - - - VAD Events - - logScrollRef.current?.scrollToEnd({ animated: true }) - } - > - {logs.length > 0 ? ( - logs.map((log, i) => ( - - {log} - - )) - ) : ( - - No events logged yet... - - )} - - - - - {isStreaming ? ( - - - Stop VAD Stream - - ) : ( - - - - {isSimulator - ? 'Recording not available on Simulator' - : 'Start VAD Stream'} - - - )} - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - backgroundColor: 'white', - paddingHorizontal: 16, - }, - header: { - alignItems: 'center', - position: 'relative', - width: '100%', - }, - backButton: { - position: 'absolute', - left: 0, - top: 10, - padding: 10, - zIndex: 1, - }, - headerText: { - fontSize: 22, - fontWeight: 'bold', - color: '#0f186e', - }, - statusContainer: { - marginTop: 12, - alignItems: 'center', - }, - visualizerContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - visualizerText: { - marginTop: 20, - fontSize: 24, - fontWeight: '800', - letterSpacing: 2, - }, - speakingText: { - color: '#22c55e', - }, - silentText: { - color: '#ef4444', - }, - logContainer: { - height: 150, - width: '100%', - marginVertical: 12, - }, - logLabel: { - marginLeft: 12, - marginBottom: 4, - color: '#0f186e', - fontWeight: '600', - }, - logScrollContainer: { - borderRadius: 12, - borderWidth: 1, - borderColor: '#0f186e', - padding: 12, - backgroundColor: '#f8fafc', - }, - logText: { - fontSize: 12, - fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', - color: '#334155', - marginBottom: 2, - }, - placeholderText: { - color: '#aaa', - fontStyle: 'italic', - }, - inputContainer: { - marginBottom: 30, - width: '100%', - }, - liveButton: { - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - padding: 16, - borderRadius: 12, - gap: 8, - }, - backgroundRed: { - backgroundColor: '#ef4444', - }, - backgroundBlue: { - backgroundColor: '#0f186e', - }, - buttonText: { - color: 'white', - fontWeight: '600', - fontSize: 16, - }, - disabled: { - opacity: 0.5, - }, -}); diff --git a/apps/speech/tsconfig.json b/apps/speech/tsconfig.json deleted file mode 100644 index bfe226172e..0000000000 --- a/apps/speech/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "strict": true, - "allowJs": true, - "module": "preserve", - "moduleDetection": "force", - "moduleResolution": "bundler", - "customConditions": ["react-native"], - "noEmit": true, - "paths": { - "react-native-executorch": ["../../packages/react-native-executorch/src"], - "react-native-executorch-expo-resource-fetcher": [ - "../../packages/expo-resource-fetcher/src" - ] - } - } -} diff --git a/apps/text-embeddings/.gitignore b/apps/text-embeddings/.gitignore deleted file mode 100644 index d7eba44af8..0000000000 --- a/apps/text-embeddings/.gitignore +++ /dev/null @@ -1,39 +0,0 @@ -# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files - -# dependencies -node_modules/ - -# Expo -.expo/ -dist/ -web-build/ -expo-env.d.ts - -# Native -*.orig.* -*.jks -*.p8 -*.p12 -*.key -*.mobileprovision - -# Metro -.metro-health-check* - -# debug -npm-debug.* -yarn-debug.* -yarn-error.* - -# macOS -.DS_Store -*.pem - -# local env files -.env*.local - -# typescript -*.tsbuildinfo - -# yarn -.yarn \ No newline at end of file diff --git a/apps/text-embeddings/app.json b/apps/text-embeddings/app.json deleted file mode 100644 index 6fe3a043b9..0000000000 --- a/apps/text-embeddings/app.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "expo": { - "name": "text-embeddings", - "slug": "text-embeddings", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/icon.png", - "userInterfaceStyle": "light", - "newArchEnabled": true, - "scheme": "rne-embeddings", - "splash": { - "image": "./assets/splash-icon.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, - "ios": { - "supportsTablet": true, - "bundleIdentifier": "com.anonymous.text-embeddings" - }, - "android": { - "adaptiveIcon": { - "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#ffffff" - }, - "package": "com.anonymous.textembeddings" - }, - "web": { - "favicon": "./assets/favicon.png" - }, - "plugins": [ - "expo-router", - [ - "expo-build-properties", - { - "ios": { - "deploymentTarget": "17.0" - } - } - ] - ] - } -} diff --git a/apps/text-embeddings/app/_layout.tsx b/apps/text-embeddings/app/_layout.tsx deleted file mode 100644 index bb8e1deeb8..0000000000 --- a/apps/text-embeddings/app/_layout.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { Drawer } from 'expo-router/drawer'; -import { initExecutorch } from 'react-native-executorch'; -import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher'; -import ColorPalette from '../colors'; -import React, { useState } from 'react'; -import { - Text, - StyleSheet, - View, - TouchableOpacity, - type ColorValue, -} from 'react-native'; - -import { - type DrawerContentComponentProps, - DrawerContentScrollView, - DrawerItemList, -} from 'expo-router/build/react-navigation/drawer'; -import { DrawerActions } from 'expo-router/build/react-navigation/routers'; -import { useNavigation } from 'expo-router'; -import Svg, { Rect } from 'react-native-svg'; -import { GeneratingContext } from '../context'; - -function HamburgerIcon({ tintColor }: { tintColor?: ColorValue }) { - const navigation = useNavigation(); - const fill = typeof tintColor === 'string' ? tintColor : '#000'; - return ( - navigation.dispatch(DrawerActions.toggleDrawer())} - style={styles.hamburger} - > - - - - - - - ); -} - -initExecutorch({ - resourceFetcher: ExpoResourceFetcher, -}); - -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 }, - headerLeft: (props) => , - }} - > - null, - title: 'Main Menu', - drawerItemStyle: { display: 'none' }, - }} - /> - - - - - ); -} - -const styles = StyleSheet.create({ - hamburger: { - marginLeft: 12, - }, - 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/text-embeddings/app/clip-embeddings/index.tsx b/apps/text-embeddings/app/clip-embeddings/index.tsx deleted file mode 100644 index 02a8a9c656..0000000000 --- a/apps/text-embeddings/app/clip-embeddings/index.tsx +++ /dev/null @@ -1,397 +0,0 @@ -import { useState } from 'react'; -import { - StyleSheet, - Text, - TextInput, - TouchableOpacity, - View, - SafeAreaView, - ScrollView, - Image, - KeyboardAvoidingView, - Platform, -} from 'react-native'; -import { Ionicons } from '@expo/vector-icons'; -import { - models, - useTextEmbeddings, - useImageEmbeddings, - ImageEmbeddingsProps, -} from 'react-native-executorch'; - -type ImageEmbeddingModel = ImageEmbeddingsProps['model']; - -const IMAGE_MODELS: { label: string; value: ImageEmbeddingModel }[] = [ - { - label: 'ViT-B/32 Quantized', - value: models.image_embedding.clip_vit_base_patch32_image(), - }, - { - label: 'ViT-B/32 FP32', - value: models.image_embedding.clip_vit_base_patch32_image({ - quant: false, - }), - }, -]; -import { launchImageLibrary } from 'react-native-image-picker'; -import { useIsFocused } from 'expo-router'; -import { dotProduct } from '../../utils/math'; -import { ModelPicker } from '../../components/ModelPicker'; - -const DEFAULT_LABELS = [ - 'a photo of a dog', - 'a photo of a cat', - 'a landscape photo', - 'a photo of food', - 'a photo of people', -]; - -export default function ClipEmbeddingsScreenWrapper() { - const isFocused = useIsFocused(); - - return isFocused ? : null; -} - -function ClipEmbeddingsScreen() { - const [selectedImageModel, setSelectedImageModel] = - useState( - models.image_embedding.clip_vit_base_patch32_image() - ); - - const textModel = useTextEmbeddings({ - model: models.text_embedding.clip_vit_base_patch32_text(), - }); - const imageModel = useImageEmbeddings({ model: selectedImageModel }); - - const [imageUri, setImageUri] = useState(null); - const [newLabel, setNewLabel] = useState(''); - const [labels, setLabels] = useState(DEFAULT_LABELS); - const [results, setResults] = useState< - { label: string; similarity: number }[] - >([]); - const [imageEmbeddingTime, setImageEmbeddingTime] = useState( - null - ); - const [textEmbeddingTime, setTextEmbeddingTime] = useState( - null - ); - - const getModelStatusText = (model: typeof textModel | typeof imageModel) => { - if (model.error) return `Oops! ${model.error}`; - if (!model.isReady) - return `Loading ${(model.downloadProgress * 100).toFixed(0)}%`; - return model.isGenerating ? 'Generating…' : 'Ready'; - }; - - const pickImage = async () => { - const output = await launchImageLibrary({ mediaType: 'photo' }); - if (!output.assets?.[0]?.uri) return; - setImageUri(output.assets[0].uri); - setResults([]); - }; - - const classify = async () => { - if (!imageUri || !imageModel.isReady || !textModel.isReady) return; - - try { - const imgStart = Date.now(); - const imageEmbedding = await imageModel.forward(imageUri); - setImageEmbeddingTime(Date.now() - imgStart); - - const txtStart = Date.now(); - const scored: { label: string; similarity: number }[] = []; - for (const label of labels) { - const textEmbedding = await textModel.forward(label); - scored.push({ - label, - similarity: dotProduct(imageEmbedding, textEmbedding), - }); - } - setTextEmbeddingTime(Math.round((Date.now() - txtStart) / labels.length)); - - scored.sort((a, b) => b.similarity - a.similarity); - setResults(scored); - } catch (e) { - console.error('Error during classification:', e); - } - }; - - const addLabel = () => { - const trimmed = newLabel.trim(); - if (!trimmed || labels.includes(trimmed)) return; - setLabels((prev) => [...prev, trimmed]); - setNewLabel(''); - setResults([]); - }; - - const removeLabel = (label: string) => { - setLabels((prev) => prev.filter((l) => l !== label)); - setResults((prev) => prev.filter((r) => r.label !== label)); - }; - - const modelsReady = textModel.isReady && imageModel.isReady; - - return ( - - - - CLIP Image Embeddings - - - - Text model: {getModelStatusText(textModel)} - - - Image model: {getModelStatusText(imageModel)} - - - - { - setSelectedImageModel(m); - setResults([]); - }} - /> - - {/* Image picker */} - - {imageUri ? ( - - ) : ( - - - - Tap to pick an image - - - )} - - - {/* Classify button */} - - - - {!imageUri ? 'Pick an image first' : 'Find best matching label'} - - - - {/* Results */} - {results.length > 0 && ( - - Results - {results.map((item, index) => ( - - - {index === 0 ? '🥇 ' : ''} - {item.label} - - - {item.similarity.toFixed(3)} - - - ))} - {(imageEmbeddingTime !== null || textEmbeddingTime !== null) && ( - - {imageEmbeddingTime !== null && ( - - Image embedding: {imageEmbeddingTime} ms - - )} - {textEmbeddingTime !== null && ( - - Text embeddings: {textEmbeddingTime} ms - - )} - - )} - - )} - - {/* Labels */} - - Text Labels - {labels.map((label) => ( - - {label} - removeLabel(label)}> - - - - ))} - - - - - - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#F8FAFC' }, - flex: { flex: 1 }, - scrollContainer: { padding: 20, alignItems: 'center', flexGrow: 1 }, - heading: { - fontSize: 24, - fontWeight: '500', - marginBottom: 12, - color: '#0F172A', - }, - statusRow: { - width: '100%', - marginBottom: 16, - gap: 2, - }, - statusText: { fontSize: 13, color: '#64748B' }, - imagePicker: { - width: '100%', - height: 220, - borderRadius: 16, - overflow: 'hidden', - marginBottom: 20, - borderWidth: 2, - borderColor: '#E2E8F0', - backgroundColor: '#fff', - }, - image: { width: '100%', height: '100%' }, - imagePlaceholder: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - gap: 8, - }, - imagePlaceholderText: { fontSize: 14, color: '#94A3B8' }, - card: { - backgroundColor: '#FFFFFF', - width: '100%', - padding: 16, - borderRadius: 16, - borderColor: '#E2E8F0', - borderWidth: 2, - marginBottom: 20, - }, - sectionTitle: { - fontSize: 16, - fontWeight: '600', - marginBottom: 12, - color: '#1E293B', - }, - labelRow: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingVertical: 6, - borderBottomWidth: 1, - borderBottomColor: '#F1F5F9', - }, - labelText: { fontSize: 14, color: '#334155', flex: 1 }, - addLabelRow: { - flexDirection: 'row', - alignItems: 'center', - gap: 8, - marginTop: 12, - }, - input: { - flex: 1, - backgroundColor: '#F1F5F9', - borderRadius: 10, - padding: 10, - fontSize: 14, - color: '#0F172A', - }, - addButton: { - backgroundColor: 'navy', - borderRadius: 10, - width: 40, - height: 40, - alignItems: 'center', - justifyContent: 'center', - }, - classifyButton: { - width: '100%', - backgroundColor: 'navy', - padding: 14, - borderRadius: 12, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - gap: 8, - marginBottom: 20, - }, - classifyButtonText: { color: 'white', fontWeight: '600', fontSize: 15 }, - buttonDisabled: { backgroundColor: '#f0f0f0', borderColor: '#d3d3d3' }, - buttonTextDisabled: { color: 'gray' }, - resultRow: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingVertical: 6, - borderBottomWidth: 1, - borderBottomColor: '#F1F5F9', - }, - resultLabel: { fontSize: 14, color: '#334155', flex: 1 }, - topResultLabel: { fontWeight: '700', color: '#0F172A' }, - resultScore: { fontSize: 13, color: '#64748B', marginLeft: 8 }, - statsContainer: { marginTop: 12, gap: 2 }, - statsText: { fontSize: 12, color: '#94A3B8' }, -}); diff --git a/apps/text-embeddings/app/index.tsx b/apps/text-embeddings/app/index.tsx deleted file mode 100644 index f661e44978..0000000000 --- a/apps/text-embeddings/app/index.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { useRouter } from 'expo-router'; -import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; -import ColorPalette from '../colors'; -import ExecutorchLogo from '../assets/executorch.svg'; - -export default function Home() { - const router = useRouter(); - - return ( - - - Select a demo model - - router.navigate('text-embeddings/')} - > - Text embeddings - - router.navigate('clip-embeddings/')} - > - Clip embeddings - - - - ); -} - -export const fontSizes = { - xxl: 34, - xl: 22, - lg: 18, - md: 16, - sm: 14, - xs: 12, - xxs: 10, -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#fff', - }, - headerText: { - fontSize: fontSizes.lg, - color: ColorPalette.strongPrimary, - margin: 20, - }, - buttonContainer: { - width: '80%', - justifyContent: 'space-evenly', - marginBottom: 20, - }, - button: { - backgroundColor: ColorPalette.strongPrimary, - borderRadius: 8, - padding: 10, - alignItems: 'center', - marginBottom: 10, - }, - buttonText: { - color: 'white', - fontSize: fontSizes.md, - }, -}); diff --git a/apps/text-embeddings/app/text-embeddings/index.tsx b/apps/text-embeddings/app/text-embeddings/index.tsx deleted file mode 100644 index 88e39ce063..0000000000 --- a/apps/text-embeddings/app/text-embeddings/index.tsx +++ /dev/null @@ -1,399 +0,0 @@ -import { useEffect, useState } from 'react'; -import { - StyleSheet, - Text, - TextInput, - TouchableOpacity, - View, - SafeAreaView, - ScrollView, - KeyboardAvoidingView, - Platform, -} from 'react-native'; -import { Ionicons } from '@expo/vector-icons'; -import { ModelPicker } from '../../components/ModelPicker'; -import { - models, - useTextEmbeddings, - TextEmbeddingsProps, -} from 'react-native-executorch'; -const textEmbedding = models.text_embedding; - -type TextEmbeddingModel = TextEmbeddingsProps['model']; - -const MODELS: { label: string; value: TextEmbeddingModel }[] = [ - { label: 'MiniLM L6', value: textEmbedding.all_minilm_l6_v2() }, - { - label: 'MPNet Base', - value: textEmbedding.all_mpnet_base_v2(), - }, - { - label: 'MultiQA MiniLM', - value: textEmbedding.multi_qa_minilm_l6_cos_v1(), - }, - { - label: 'MultiQA MPNet', - value: textEmbedding.multi_qa_mpnet_base_dot_v1(), - }, - { - label: 'Multilingual DistilUSE', - value: textEmbedding.distiluse_base_multilingual_cased_v2(), - }, - { - label: 'Multilingual Paraphrase', - value: textEmbedding.paraphrase_multilingual_minilm_l12_v2(), - }, -]; -import { useIsFocused } from 'expo-router'; -import { dotProduct } from '../../utils/math'; -import ErrorBanner from '../../components/ErrorBanner'; - -export default function TextEmbeddingsScreenWrapper() { - const isFocused = useIsFocused(); - - return isFocused ? : null; -} - -function TextEmbeddingsScreen() { - const [selectedModel, setSelectedModel] = useState( - textEmbedding.all_minilm_l6_v2() - ); - const model = useTextEmbeddings({ model: selectedModel }); - const [error, setError] = useState(null); - - const [inputSentence, setInputSentence] = useState(''); - const [sentencesWithEmbeddings, setSentencesWithEmbeddings] = useState< - { sentence: string; embedding: Float32Array }[] - >([]); - const [topMatches, setTopMatches] = useState< - { sentence: string; similarity: number }[] - >([]); - const [embeddingTime, setEmbeddingTime] = useState(null); - - useEffect( - () => { - const computeEmbeddings = async () => { - if (!model.isReady) return; - - const sentences = [ - 'The weather is lovely today.', - "It's so sunny outside!", - 'He drove to the stadium.', - ]; - - try { - const embeddings = []; - for (const sentence of sentences) { - const embedding = await model.forward(sentence); - embeddings.push({ sentence, embedding }); - } - - setSentencesWithEmbeddings(embeddings); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - }; - - computeEmbeddings(); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [model.isReady] - ); - - const checkSimilarities = async () => { - if (!model.isReady || !inputSentence.trim()) return; - - try { - const start = Date.now(); - const inputEmbedding = await model.forward(inputSentence); - setEmbeddingTime(Date.now() - start); - const matches = sentencesWithEmbeddings.map( - ({ sentence, embedding }) => ({ - sentence, - similarity: dotProduct(inputEmbedding, embedding), - }) - ); - matches.sort((a, b) => b.similarity - a.similarity); - setTopMatches(matches.slice(0, 3)); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - }; - - const addToSentences = async () => { - if (!model.isReady || !inputSentence.trim()) return; - - try { - const start = Date.now(); - const embedding = await model.forward(inputSentence); - setEmbeddingTime(Date.now() - start); - setSentencesWithEmbeddings((prev) => [ - ...prev, - { sentence: inputSentence, embedding }, - ]); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - - setInputSentence(''); - setTopMatches([]); - }; - - const clearList = async () => { - if (!model.isReady) return; - try { - setSentencesWithEmbeddings([]); - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); - } - }; - - const getModelStatusText = () => { - if (model.error) { - return `Oops! Error: ${model.error}`; - } - if (!model.isReady) { - return `Loading model ${(model.downloadProgress * 100).toFixed(2)}%`; - } - return model.isGenerating ? 'Generating...' : 'Model is ready'; - }; - - return ( - - - - Text Embeddings Playground - {getModelStatusText()} - { - setSelectedModel(m); - setSentencesWithEmbeddings([]); - setTopMatches([]); - }} - /> - setError(null)} /> - - - List of Existing Sentences - {sentencesWithEmbeddings.map((item, index) => ( - - - {item.sentence} - - ))} - - - Try Your Sentence - - - - - - Find Similar - - - - - - - Add to List - - - - - - Clear List - - - - - {embeddingTime !== null && ( - - Embedding time: {embeddingTime} ms - - )} - {topMatches.length > 0 && ( - - Top Matches - {topMatches.map((item, index) => ( - - {item.sentence} ({item.similarity.toFixed(2)}) - - ))} - - )} - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#F8FAFC', - }, - scrollContainer: { - padding: 20, - alignItems: 'center', - flexGrow: 1, - }, - heading: { - fontSize: 24, - fontWeight: '500', - marginBottom: 20, - color: '#0F172A', - }, - card: { - backgroundColor: '#FFFFFF', - width: '100%', - padding: 16, - borderRadius: 16, - borderColor: '#E2E8F0', - borderWidth: 2, - marginBottom: 20, - }, - sectionTitle: { - fontSize: 18, - fontWeight: '500', - marginBottom: 12, - color: '#1E293B', - }, - sentenceText: { - fontSize: 14, - marginBottom: 6, - color: '#334155', - }, - input: { - backgroundColor: '#F1F5F9', - borderRadius: 10, - padding: 10, - marginBottom: 10, - fontSize: 16, - color: '#0F172A', - minHeight: 40, - textAlignVertical: 'top', - }, - buttonContainer: { - width: '100%', - gap: 10, - }, - buttonGroup: { - flexDirection: 'row', - justifyContent: 'space-between', - gap: 10, - }, - buttonPrimary: { - flex: 1, - backgroundColor: 'navy', - padding: 12, - borderRadius: 10, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - }, - buttonSecondary: { - flex: 1, - backgroundColor: 'transparent', - borderWidth: 2, - borderColor: 'navy', - padding: 12, - borderRadius: 10, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - }, - buttonDisabled: { - backgroundColor: '#f0f0f0', - borderColor: '#d3d3d3', - }, - buttonText: { - color: 'white', - textAlign: 'center', - fontWeight: '500', - }, - buttonTextOutline: { - color: 'navy', - textAlign: 'center', - fontWeight: '500', - }, - buttonTextDisabled: { - color: 'gray', - }, - topMatchesContainer: { - marginTop: 20, - }, - statsText: { - fontSize: 13, - color: '#64748B', - marginTop: 8, - textAlign: 'center', - }, - flexContainer: { - flex: 1, - }, -}); diff --git a/apps/text-embeddings/assets/adaptive-icon.png b/apps/text-embeddings/assets/adaptive-icon.png deleted file mode 100644 index 03d6f6b6c6..0000000000 Binary files a/apps/text-embeddings/assets/adaptive-icon.png and /dev/null differ diff --git a/apps/text-embeddings/assets/executorch.svg b/apps/text-embeddings/assets/executorch.svg deleted file mode 100644 index e548ea4201..0000000000 --- a/apps/text-embeddings/assets/executorch.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/apps/text-embeddings/assets/favicon.png b/apps/text-embeddings/assets/favicon.png deleted file mode 100644 index e75f697b18..0000000000 Binary files a/apps/text-embeddings/assets/favicon.png and /dev/null differ diff --git a/apps/text-embeddings/assets/icon.png b/apps/text-embeddings/assets/icon.png deleted file mode 100644 index a0b1526fc7..0000000000 Binary files a/apps/text-embeddings/assets/icon.png and /dev/null differ diff --git a/apps/text-embeddings/assets/splash-icon.png b/apps/text-embeddings/assets/splash-icon.png deleted file mode 100644 index 03d6f6b6c6..0000000000 Binary files a/apps/text-embeddings/assets/splash-icon.png and /dev/null differ diff --git a/apps/text-embeddings/babel.config.js b/apps/text-embeddings/babel.config.js deleted file mode 100644 index 9d89e13119..0000000000 --- a/apps/text-embeddings/babel.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = function (api) { - api.cache(true); - return { - presets: ['babel-preset-expo'], - }; -}; diff --git a/apps/text-embeddings/colors.ts b/apps/text-embeddings/colors.ts deleted file mode 100644 index 03defcda9e..0000000000 --- a/apps/text-embeddings/colors.ts +++ /dev/null @@ -1,11 +0,0 @@ -const ColorPalette = { - primary: '#001A72', - seaBlueLight: '#E1F3FA', - seaBlueMedium: '#B5E1F1', - seaBlueDark: '#87CCE8', - blueLight: '#C1C6E5', - blueDark: '#6676AA', - strongPrimary: '#020F3C', -}; - -export default ColorPalette; diff --git a/apps/text-embeddings/components/ErrorBanner.tsx b/apps/text-embeddings/components/ErrorBanner.tsx deleted file mode 100644 index a5bebc504f..0000000000 --- a/apps/text-embeddings/components/ErrorBanner.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; - -interface ErrorBannerProps { - message: string | null; - onDismiss: () => void; -} - -export default function ErrorBanner({ message, onDismiss }: ErrorBannerProps) { - if (!message) return null; - - return ( - - - {message} - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - backgroundColor: '#FEE2E2', - borderLeftWidth: 4, - borderLeftColor: '#EF4444', - borderRadius: 8, - marginHorizontal: 16, - marginVertical: 8, - paddingVertical: 10, - paddingLeft: 12, - paddingRight: 8, - flexDirection: 'row', - alignItems: 'center', - }, - message: { - flex: 1, - color: '#991B1B', - fontSize: 14, - lineHeight: 20, - }, - closeButton: { - padding: 4, - marginLeft: 8, - }, - closeText: { - color: '#991B1B', - fontSize: 16, - fontWeight: '600', - }, -}); diff --git a/apps/text-embeddings/components/ModelPicker.tsx b/apps/text-embeddings/components/ModelPicker.tsx deleted file mode 100644 index 94a848596e..0000000000 --- a/apps/text-embeddings/components/ModelPicker.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { - Dimensions, - Modal, - ScrollView, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; - -export type ModelOption = { - label: string; - value: T; -}; - -type Props = { - models: ModelOption[]; - selectedModel: T; - onSelect: (model: T) => void; - label?: string; - disabled?: boolean; -}; - -const DROPDOWN_MAX_HEIGHT = 200; - -export function ModelPicker({ - models, - selectedModel, - onSelect, - label, - disabled, -}: Props) { - const [open, setOpen] = useState(false); - const [triggerHeight, setTriggerHeight] = useState(0); - const [expandUp, setExpandUp] = useState(false); - const [dropdownTop, setDropdownTop] = useState(0); - const triggerRef = useRef>(null); - const selected = models.find((m) => m.value === selectedModel); - - useEffect(() => { - if (disabled) setOpen(false); - }, [disabled]); - - const handlePress = () => { - if (disabled) return; - if (open) { - setOpen(false); - return; - } - triggerRef.current?.measure( - ( - _x: number, - _y: number, - _width: number, - height: number, - _pageX: number, - pageY: number - ) => { - setTriggerHeight(height); - const spaceBelow = Dimensions.get('window').height - (pageY + height); - setExpandUp(spaceBelow < DROPDOWN_MAX_HEIGHT); - setDropdownTop(pageY); - setOpen(true); - } - ); - }; - - const dropdownStylePosition = expandUp - ? { - bottom: Dimensions.get('window').height - dropdownTop, - left: 12, - right: 12, - } - : { - top: dropdownTop + triggerHeight + 2, - left: 12, - right: 12, - }; - - return ( - <> - - - {label && {label}} - {selected?.label ?? '—'} - {open ? '▲' : '▼'} - - - - {open && ( - setOpen(false)} - animationType="none" - > - setOpen(false)} - /> - - - {models.map((item) => { - const isSelected = item.value === selectedModel; - return ( - { - onSelect(item.value); - setOpen(false); - }} - activeOpacity={0.7} - > - - {item.label} - - - ); - })} - - - - )} - - ); -} - -const styles = StyleSheet.create({ - container: { - marginHorizontal: 12, - marginVertical: 4, - alignSelf: 'stretch', - zIndex: 100, - }, - trigger: { - flexDirection: 'row', - alignItems: 'center', - borderWidth: 1, - borderColor: '#C1C6E5', - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 10, - backgroundColor: '#f5f5f5', - }, - triggerDisabled: { - opacity: 0.4, - }, - label: { - fontSize: 12, - color: '#888', - marginRight: 6, - }, - triggerText: { - flex: 1, - fontSize: 14, - color: '#001A72', - fontWeight: '500', - }, - chevron: { - fontSize: 10, - color: '#888', - marginLeft: 6, - }, - modalBackdrop: { - flex: 1, - backgroundColor: 'rgba(0, 0, 0, 0.3)', - }, - dropdown: { - position: 'absolute', - borderWidth: 1, - borderColor: '#C1C6E5', - borderRadius: 8, - backgroundColor: '#fff', - maxHeight: DROPDOWN_MAX_HEIGHT, - zIndex: 1000, - elevation: 5, - shadowColor: '#000', - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.15, - shadowRadius: 6, - }, - option: { - paddingHorizontal: 12, - paddingVertical: 10, - borderBottomWidth: 1, - borderBottomColor: '#f0f0f0', - }, - optionSelected: { - backgroundColor: '#e8ecf8', - }, - optionText: { - fontSize: 14, - color: '#333', - }, - optionTextSelected: { - color: '#001A72', - fontWeight: '600', - }, -}); diff --git a/apps/text-embeddings/context.ts b/apps/text-embeddings/context.ts deleted file mode 100644 index 0eb0560768..0000000000 --- a/apps/text-embeddings/context.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createContext } from 'react'; - -export const GeneratingContext = createContext({ - setGlobalGenerating: (_newState: boolean) => {}, -}); diff --git a/apps/text-embeddings/declarations.d.ts b/apps/text-embeddings/declarations.d.ts deleted file mode 100644 index 85e178f497..0000000000 --- a/apps/text-embeddings/declarations.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module '*.svg' { - import { SvgProps } from 'react-native-svg'; - const content: React.FV; - export default content; -} diff --git a/apps/text-embeddings/index.ts b/apps/text-embeddings/index.ts deleted file mode 100644 index 3f443dcf95..0000000000 --- a/apps/text-embeddings/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { registerRootComponent } from 'expo'; - -import App from './app'; - -// registerRootComponent calls AppRegistry.registerComponent('main', () => App); -// It also ensures that whether you load the app in Expo Go or in a native build, -// the environment is set up appropriately -registerRootComponent(App); diff --git a/apps/text-embeddings/metro.config.js b/apps/text-embeddings/metro.config.js deleted file mode 100644 index f8ab2ab96d..0000000000 --- a/apps/text-embeddings/metro.config.js +++ /dev/null @@ -1,21 +0,0 @@ -// Learn more https://docs.expo.io/guides/customizing-metro -const { getDefaultConfig } = require('expo/metro-config'); - -/** @type {import('expo/metro-config').MetroConfig} */ -const config = getDefaultConfig(__dirname); - -const { transformer, resolver } = config; - -config.transformer = { - ...transformer, - babelTransformerPath: require.resolve('react-native-svg-transformer/expo'), -}; -config.resolver = { - ...resolver, - assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'), - sourceExts: [...resolver.sourceExts, 'svg'], -}; - -config.resolver.assetExts.push('pte'); - -module.exports = config; diff --git a/apps/text-embeddings/package.json b/apps/text-embeddings/package.json deleted file mode 100644 index f49ff45e3f..0000000000 --- a/apps/text-embeddings/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "text-embeddings", - "version": "1.0.0", - "main": "expo-router/entry", - "scripts": { - "start": "expo start", - "android": "expo run:android", - "ios": "expo run:ios", - "web": "expo start --web", - "typecheck": "tsc", - "lint": "eslint . --ext .ts,.tsx --fix" - }, - "dependencies": { - "@expo/log-box": "~56.0.12", - "@expo/vector-icons": "^15.0.2", - "@react-native/metro-config": "^0.85.3", - "@react-navigation/drawer": "^7.9.4", - "@react-navigation/native": "^7.2.2", - "expo": "~56.0.9", - "expo-build-properties": "~56.0.17", - "expo-constants": "~56.0.17", - "expo-font": "~56.0.5", - "expo-linking": "~56.0.13", - "expo-router": "~56.2.9", - "expo-status-bar": "~56.0.4", - "react": "19.2.3", - "react-native": "0.85.3", - "react-native-executorch": "workspace:*", - "react-native-executorch-expo-resource-fetcher": "workspace:*", - "react-native-gesture-handler": "~2.31.1", - "react-native-image-picker": "^8.2.1", - "react-native-reanimated": "4.3.1", - "react-native-safe-area-context": "~5.7.0", - "react-native-screens": "~4.25.2", - "react-native-svg": "15.15.4", - "react-native-worklets": "0.8.3" - }, - "devDependencies": { - "@babel/core": "^7.29.0", - "@types/react": "~19.2.14", - "@types/react-refresh": "^0", - "babel-preset-expo": "~56.0.14", - "react-refresh": "^0.18.0" - }, - "private": true -} diff --git a/apps/text-embeddings/tsconfig.json b/apps/text-embeddings/tsconfig.json deleted file mode 100644 index bfe226172e..0000000000 --- a/apps/text-embeddings/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "strict": true, - "allowJs": true, - "module": "preserve", - "moduleDetection": "force", - "moduleResolution": "bundler", - "customConditions": ["react-native"], - "noEmit": true, - "paths": { - "react-native-executorch": ["../../packages/react-native-executorch/src"], - "react-native-executorch-expo-resource-fetcher": [ - "../../packages/expo-resource-fetcher/src" - ] - } - } -} diff --git a/apps/text-embeddings/utils/math.ts b/apps/text-embeddings/utils/math.ts deleted file mode 100644 index 50c70d1f92..0000000000 --- a/apps/text-embeddings/utils/math.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - RnExecutorchError, - RnExecutorchErrorCode, -} from 'react-native-executorch'; - -export const dotProduct = (a: Float32Array, b: Float32Array) => { - if (a.length !== b.length) { - throw new RnExecutorchError( - RnExecutorchErrorCode.WrongDimensions, - `dotProduct needs both vector to have the same length: got a: ${a.length}, b: ${b.length}` - ); - } - - let sum = 0; - for (let i = 0; i < a.length; i++) { - sum += a[i] * b[i]; - } - return sum; -}; diff --git a/docs/docs/01-fundamentals/01-getting-started.md b/docs/docs/01-fundamentals/01-getting-started.md deleted file mode 100644 index 2506c8cd65..0000000000 --- a/docs/docs/01-fundamentals/01-getting-started.md +++ /dev/null @@ -1,216 +0,0 @@ ---- -title: Getting Started -slug: /fundamentals/getting-started -keywords: - [ - react native, - react native ai, - react native llm, - react native qwen, - react native llama, - react native executorch, - executorch, - on-device ai, - pytorch, - mobile ai, - ] -description: 'Get started with React Native ExecuTorch - a framework for running AI models on-device in your React Native applications.' ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -## What is ExecuTorch? - -[ExecuTorch](https://executorch.ai) is a novel AI framework developed by Meta, designed to streamline deploying PyTorch models on a variety of devices, including mobile phones and microcontrollers. This framework enables exporting models into standalone binaries, allowing them to run locally without requiring API calls. ExecuTorch achieves state-of-the-art performance through optimizations and delegates such as Core ML and XNNPACK. It provides a seamless export process with robust debugging options, making it easier to resolve issues if they arise. - -## React Native ExecuTorch - -React Native ExecuTorch is our way of bringing ExecuTorch into the React Native world. Our API is built to be simple, declarative, and efficient. Additionally, we provide a set of pre-exported models for common use cases, so you don't have to worry about handling exports yourself. With just a few lines of JavaScript, you can run AI models (even LLMs 👀) right on your device—keeping user data private and saving on cloud costs. - -## Compatibility - -React Native Executorch supports only the [New React Native architecture](https://reactnative.dev/architecture/landing-page). - -If your app still runs on the old architecture, please consider upgrading to the New Architecture. - -For supported React Native and Expo versions, see the [Compatibility table](../07-other/01-compatibility.mdx). - -## Installation - -Installation takes two steps: install the core package, then install a resource fetcher adapter that matches your project type. If you want to implement your own model fetching logic instead, see [this document](../08-resource-fetcher/02-custom-adapter.md). - -### 1. Install the core package - - - - - ```bash - npm install react-native-executorch - ``` - - - - - ```bash - pnpm add react-native-executorch - ``` - - - - - ```bash - yarn add react-native-executorch - ``` - - - - -### 2. Install a resource fetcher - -Pick the adapter that matches your project. We recommend the Expo adapter when your app uses Expo; use the bare adapter for projects without Expo. - -#### Expo projects - - - - - ```bash - npm install react-native-executorch-expo-resource-fetcher expo-file-system expo-asset - ``` - - - - - ```bash - pnpm add react-native-executorch-expo-resource-fetcher expo-file-system expo-asset - ``` - - - - - ```bash - yarn add react-native-executorch-expo-resource-fetcher expo-file-system expo-asset - ``` - - - - -#### Bare React Native projects - - - - - ```bash - npm install react-native-executorch-bare-resource-fetcher @dr.pogodin/react-native-fs @kesha-antonov/react-native-background-downloader - ``` - - - - - ```bash - pnpm add react-native-executorch-bare-resource-fetcher @dr.pogodin/react-native-fs @kesha-antonov/react-native-background-downloader - ``` - - - - - ```bash - yarn add react-native-executorch-bare-resource-fetcher @dr.pogodin/react-native-fs @kesha-antonov/react-native-background-downloader - ``` - - - - -:::warning -Before using any other API, you must call `initExecutorch` with a resource fetcher adapter at the entry point of your app: - -```js -import { initExecutorch } from 'react-native-executorch'; -import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher'; // Use /legacy import if you're using Expo SDK < 54 -// or BareResourceFetcher for bare react-native projects - -initExecutorch({ resourceFetcher: ExpoResourceFetcher }); -``` - -Calling any library API without initializing first will throw a `ResourceFetcherAdapterNotInitialized` error. -::: - -Our library offers support for both bare React Native and Expo projects. Please follow the instructions from [Loading models section](./02-loading-models.md) to make sure you setup your project correctly. We encourage you to use Expo project if possible. If you are planning to migrate from bare React Native to Expo project, the link (https://docs.expo.dev/bare/installing-expo-modules/) offers a guidance on setting up Expo Modules in a bare React Native environment. - -If you plan on using your models via require() instead of fetching them from a url, you also need to add following lines to your `metro.config.js`: - -```json -// metro.config.js -... - defaultConfig.resolver.assetExts.push('pte') - defaultConfig.resolver.assetExts.push('bin') -... -``` - -This allows us to use binaries, such as exported models or tokenizers for LLMs. - -:::warning -When using Expo, please note that you need to use a custom development build of your app, not the standard Expo Go app. This is because we rely on native modules, which Expo Go doesn’t support. -::: - -:::info -Because we are using ExecuTorch under the hood, you won't be able to build iOS app for release with simulator selected as the target device. Make sure to test release builds on real devices. -::: - -Running the app with the library: - - - - - ```bash - npm run -- -d - ``` - - - - - ```bash - pnpm -d - ``` - - - - - ```bash - yarn -d - ``` - - - - -## Building from source - -To build the library from source instead, clone the repository and initialize submodules: - -```bash -git clone -b release/0.9 https://github.com/software-mansion/react-native-executorch.git -cd react-native-executorch - -git submodule update --init --recursive packages/react-native-executorch/third-party/common - -yarn -``` - -## Supporting new models in React Native ExecuTorch - -Adding new functionality to the library follows a consistent three-step integration pipeline: - -1. **Model Serialization:** Export PyTorch model for a specific task (e.g. object detection) into the `*.pte` format, which is optimized for the ExecuTorch runtime. - -2. **Native Implementation:** Develop a C++ execution layer that interfaces with the ExecuTorch runtime to handle inference. This layer also manages model-dependent logic, such as data pre-processing and post-processing. - -3. **TS Bindings:** Finally, implement a TypeScript API that bridges the JavaScript environment to the native C++ logic, providing a clean, typed interface for the end user. - -## Good reads - -If you want to dive deeper into ExecuTorch or our previous work with the framework, we highly encourage you to check out the following resources: - -- [ExecuTorch docs](https://pytorch.org/executorch/stable/index.html) -- [React Native RAG](https://blog.swmansion.com/introducing-react-native-rag-fbb62efa4991) -- [Offline Text Recognition on Mobile: How We Brought EasyOCR to React Native ExecuTorch](https://blog.swmansion.com/bringing-easyocr-to-react-native-executorch-2401c09c2d0c) diff --git a/docs/docs/01-fundamentals/02-loading-models.md b/docs/docs/01-fundamentals/02-loading-models.md deleted file mode 100644 index f80a484104..0000000000 --- a/docs/docs/01-fundamentals/02-loading-models.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: Loading Models ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -There are three different methods available for loading model files, depending on their size and location. - -## Prerequisites - -In our library, you can use two different resource fetching mechanisms. One is implemented using Expo FileSystem, the other one uses external library. We encourage you to use implementation utilizing Expo if possible. - -To use the Expo adapter, please add these libraries: - - - - - ```bash - npm install react-native-executorch-expo-resource-fetcher expo-file-system expo-asset - ``` - - - - - ```bash - pnpm add react-native-executorch-expo-resource-fetcher expo-file-system expo-asset - ``` - - - - - ```bash - yarn add react-native-executorch-expo-resource-fetcher expo-file-system expo-asset - ``` - - - - -and then add the following code in your React Native app: - -```typescript -import { initExecutorch } from 'react-native-executorch'; -import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher'; // Use /legacy import if you're using Expo SDK < 54 - -initExecutorch({ - resourceFetcher: ExpoResourceFetcher, -}); -``` - -If you cannot use Expo in your project, proceed with the following steps: - - - - - ```bash - npm install react-native-executorch-bare-resource-fetcher @dr.pogodin/react-native-fs @kesha-antonov/react-native-background-downloader - ``` - - - - - ```bash - pnpm add react-native-executorch-bare-resource-fetcher @dr.pogodin/react-native-fs @kesha-antonov/react-native-background-downloader - ``` - - - - - ```bash - yarn add react-native-executorch-bare-resource-fetcher @dr.pogodin/react-native-fs @kesha-antonov/react-native-background-downloader - ``` - - - - -and - -```typescript -import { initExecutorch } from 'react-native-executorch'; -import { BareResourceFetcher } from 'react-native-executorch-bare-resource-fetcher'; - -initExecutorch({ - resourceFetcher: BareResourceFetcher, -}); -``` - -## Loading - -### Load from React Native assets folder (for files < 512MB) - -```typescript -useExecutorchModule({ - modelSource: require('../assets/lfm2_5.pte'), -}); -``` - -### Load from remote URL - -For files larger than 512MB or when you want to keep size of the app smaller, you can load the model from a remote URL (e.g. HuggingFace). - -```typescript -useExecutorchModule({ - modelSource: 'https://.../lfm2_5.pte', -}); -``` - -### Load from local file system - -If you prefer to delegate the process of obtaining and loading model and tokenizer files to the user, you can use the following method: - -```typescript -useExecutorchModule({ - modelSource: 'file:///var/mobile/.../lfm2_5.pte', -}); -``` - -:::note -The downloaded files are stored in documents directory of your application. -::: - -## Predefined Models - -Our library offers out-of-the-box support for multiple models. To make things easier, we created aliases for our model exported to `pte` format. For full list of aliases, check out: [API Reference](../06-api-reference/index.md#models---classification) - -## Example - -The following code snippet demonstrates how to load model and tokenizer files using `useLLM` hook: - -```typescript -import { models, useLLM } from 'react-native-executorch'; -const llm = useLLM({ model: models.llm.lfm2_5_1_2b_instruct() }); -``` diff --git a/docs/docs/01-fundamentals/03-frequently-asked-questions.md b/docs/docs/01-fundamentals/03-frequently-asked-questions.md deleted file mode 100644 index 69e3792d41..0000000000 --- a/docs/docs/01-fundamentals/03-frequently-asked-questions.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Frequently Asked Questions ---- - -This section is meant to answer some common community inquiries, especially regarding the ExecuTorch runtime or adding your own models. If you can't see an answer to your question, feel free to open up a [discussion](https://github.com/software-mansion/react-native-executorch/discussions/new/choose). - -### What models are supported? - -Each hook documentation subpage (useClassification, useLLM, etc.) contains a supported models section, which lists the models that are runnable within the library with close to no setup. For running your custom models, refer to `ExecuTorchModule` or `useExecuTorchModule`. - -### How can I run my own AI model? - -To run your own model, you need to directly access the underlying [ExecuTorch Module API](https://pytorch.org/executorch/stable/extension-module.html). We provide [React hook](../03-hooks/03-executorch-bindings/useExecutorchModule.md) along with a [TypeScript alternative](../04-typescript-api/03-executorch-bindings/ExecutorchModule.md), which serve as a way to use the aforementioned API without the need of diving into native code. In order to get a model in a format runnable by the runtime, you'll need to get your hands dirty with some ExecuTorch knowledge. For more guides on exporting models, please refer to the [ExecuTorch tutorials](https://pytorch.org/executorch/stable/tutorials/export-to-executorch-tutorial.html). Once you obtain your model in a `.pte` format, you can run it with `useExecuTorchModule` and `ExecuTorchModule`. - -### How React Native ExecuTorch works under the hood? - -The general workflow for each functionality in our library goes like this: - -- You call a functionality from TypeScript -- TypeScript calls C++ function like model inference or data processing via JSI -- C++ returns result to TypeScript back via JSI -- You get results in TypeScript - -Using JSI enables us using **zero-copy data transfer** and **fast, low-level C++**. - -### Can you do function calling with useLLM? - -If your model supports tool calling (i.e. its chat template can process tools) you can use the method explained on the [useLLM page](../03-hooks/01-natural-language-processing/useLLM.md). - -If your model doesn't support it, you can still work around it using context. For details, refer to [this comment](https://github.com/software-mansion/react-native-executorch/issues/173#issuecomment-2775082278). - -### Can I use React Native ExecuTorch in bare React Native apps? - -Yes, staring from version `0.8.x` you can use React Native ExecuTorch in bare React Native apps. You just need to use bare React Native resource fetcher instead of Expo one, see: [Loading models section](./02-loading-models.md) for more details. - -### Do you support the old architecture? - -The old architecture is not supported and we're currently not planning to add support. - -### Can I run GGUF models using the library? - -No, as of now ExecuTorch runtime doesn't provide a reliable way to use GGUF models, hence it is not possible. - -### Are the models leveraging GPU acceleration? - -While it is possible to run some models using Core ML on iOS, which is a backend that utilizes CPU, GPU and ANE, we currently don't have many models exported to Core ML. For Android, the current state of GPU acceleration is pretty limited. As of now, there are attempts of running the models using a Vulkan backend. However the operator support is very limited meaning that the resulting performance is often inferior to XNNPACK. Hence, most of the models use XNNPACK, which is a highly optimized and mature CPU backend that runs on both Android and iOS. - -### Does this library support XNNPACK and Core ML? - -Yes, all of the backends are linked, therefore the only thing that needs to be done on your end is to export the model with the backend that you're interested in using. diff --git a/docs/docs/01-fundamentals/04-glossary-of-terms.md b/docs/docs/01-fundamentals/04-glossary-of-terms.md deleted file mode 100644 index 40d7e049e7..0000000000 --- a/docs/docs/01-fundamentals/04-glossary-of-terms.md +++ /dev/null @@ -1,59 +0,0 @@ -# Glossary of Terms - -This glossary defines key concepts used throughout the React Native ExecuTorch ecosystem, covering high-level machine learning terms and library-specific components. - -## Backend - -The execution engine responsible for running the actual computations of a model on specific hardware. - -- **XNNPACK:** A highly optimized library for floating-point neural network inference on ARM, x86, and WebAssembly. It is the default CPU backend for ExecuTorch. - -- **Core ML:** Apple's framework for optimizing and running machine learning models on iOS, macOS, and iPadOS devices. Using the Core ML backend allows ExecuTorch to delegate operations to the Apple Neural Engine (ANE) for significantly faster and more energy-efficient inference. - -## Forward Function - -The primary method of a PyTorch module (usually `forward()`) that defines the computation performed at every call. In the context of ExecuTorch, this is the logic that gets exported and compiled. When you run inference in React Native, you are essentially invoking this compiled forward function with new inputs. - -## Inference - -The process of using a trained machine learning model to make predictions or generate outputs for given input data. - -## Out-of-the-Box Support - -Refers to features, models, or architectures that work immediately with React Native ExecuTorch without requiring custom compilation, manual kernel registration, or complex configuration. For example, standard Llama architectures have out-of-the-box support, meaning you can download the `.pte` file and run it instantly. - -## Prefill - -The initial phase of generating text with an LLM (Large Language Model) where the model processes the entire input prompt (context) at once. - -- **Why it matters:** This step is computationally intensive because the model must "understand" all provided tokens simultaneously. - -- **Performance Metric:** "Time to First Token" (TTFT) usually measures the speed of the prefill phase. - -## Quantization - -A technique to reduce the size of a model and speed up inference by representing weights and activations with lower-precision data types (e.g., converting 32-bit floating-point numbers to 8-bit integers). - -- **Benefits:** Drastically lowers memory usage (RAM) and saves battery life on mobile devices. - -- **Trade-off:** Slight reduction in model accuracy, though often negligible for deployment. - -## Tensor - -The fundamental data structure in PyTorch and ExecuTorch. A tensor is a multi-dimensional array (like a matrix) that holds the inputs, weights, and outputs of a model. - -- **Example:** An image might be represented as a tensor of shape `[3, 224, 224]` (3 color channels, 224 pixels high, 224 pixels wide). - -## Token - -The basic unit of text that an LLM reads and generates. A token can be a word, part of a word, or even a single character. - -- **Rule of thumb:** 1,000 tokens is roughly equivalent to 750 words in English. - -- **Context:** Models have a "Context Window" limit (e.g., 2048 tokens), which is the maximum number of tokens they can remember from the conversation history. - -## Tokenization - -The process of converting raw text (strings) into a sequence of numerical IDs (tokens) that the model can understand. - -- **TokenizerModule (Component):** In React Native ExecuTorch, the `Tokenizer` is a utility class that handles encoding text into tensors and decoding output tensors back into readable text strings. diff --git a/docs/docs/01-fundamentals/_category_.json b/docs/docs/01-fundamentals/_category_.json deleted file mode 100644 index e3fddcbebd..0000000000 --- a/docs/docs/01-fundamentals/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Fundamentals", - "link": { - "type": "generated-index" - } -} diff --git a/docs/docs/02-benchmarks/_category_.json b/docs/docs/02-benchmarks/_category_.json deleted file mode 100644 index 6ea5e9670a..0000000000 --- a/docs/docs/02-benchmarks/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Benchmarks", - "link": { - "type": "generated-index" - } -} diff --git a/docs/docs/02-benchmarks/inference-time.md b/docs/docs/02-benchmarks/inference-time.md deleted file mode 100644 index 24923bffde..0000000000 --- a/docs/docs/02-benchmarks/inference-time.md +++ /dev/null @@ -1,255 +0,0 @@ ---- -title: Inference Time ---- - -:::info -Times presented in the tables are measured as consecutive runs of the model. -Initial run times may be up to 2x longer due to model loading and -initialization. - -Inference times are measured directly from native C++ code, wrapping only the -model's forward pass, excluding input-dependent pre- and post-processing (e.g. -image resizing, normalization) and any overhead from React Native runtime. -::: - -## Classification - -:::note -For this model all input images, whether larger or smaller, are resized before -processing. Resizing is typically fast for small images but may be noticeably -slower for very large images, which can increase total time. -::: - -| Model / Device | iPhone 17 Pro [ms] | Google Pixel 10 [ms] | -| :------------------------------- | :----------------: | :------------------: | -| EFFICIENTNET_V2_S (XNNPACK FP32) | 70 | 100 | -| EFFICIENTNET_V2_S (XNNPACK INT8) | 22 | 38 | -| EFFICIENTNET_V2_S (Core ML FP32) | 12 | - | -| EFFICIENTNET_V2_S (Core ML FP16) | 5 | - | - -## Object Detection - -:::note -For this model all input images, whether larger or smaller, are resized before -processing. Resizing is typically fast for small images but may be noticeably -slower for very large images, which can increase total time. - -Times presented in the tables are measured for YOLO models with input size equal to 512. Other input sizes may yield slower or faster inference times. RF-DETR Nano uses a fixed resolution of 312×312. -::: - -| Model / Device | iPhone 17 Pro [ms] | Google Pixel 10 [ms] | -| :-------------------------------------------- | :----------------: | :------------------: | -| SSDLITE_320_MOBILENET_V3_LARGE (XNNPACK FP32) | 20 | 18 | -| SSDLITE_320_MOBILENET_V3_LARGE (Core ML FP32) | 18 | - | -| SSDLITE_320_MOBILENET_V3_LARGE (Core ML FP16) | 8 | - | -| RF_DETR_NANO (XNNPACK FP32) | 101 | 277 | -| YOLO26N (XNNPACK FP32) | 29 | 38 | -| YOLO26S (XNNPACK FP32) | 60 | 72 | -| YOLO26M (XNNPACK FP32) | 134 | 177 | -| YOLO26L (XNNPACK FP32) | 169 | 216 | -| YOLO26X (XNNPACK FP32) | 371 | 434 | - -## Style Transfer - -:::note -For this model all input images, whether larger or smaller, are resized before -processing. Resizing is typically fast for small images but may be noticeably -slower for very large images, which can increase total time. -::: - -| Model / Device | iPhone 17 Pro [ms] | Google Pixel 10 [ms] | -| :------------------------------------------ | :----------------: | :------------------: | -| STYLE_TRANSFER_CANDY (XNNPACK FP32) | 1192 | 1025 | -| STYLE_TRANSFER_CANDY (XNNPACK INT8) | 272 | 430 | -| STYLE_TRANSFER_CANDY (Core ML FP32) | 100 | - | -| STYLE_TRANSFER_CANDY (Core ML FP16) | 150 | - | -| STYLE_TRANSFER_MOSAIC (XNNPACK FP32) | 1192 | 1025 | -| STYLE_TRANSFER_MOSAIC (XNNPACK INT8) | 272 | 430 | -| STYLE_TRANSFER_MOSAIC (Core ML FP32) | 100 | - | -| STYLE_TRANSFER_MOSAIC (Core ML FP16) | 150 | - | -| STYLE_TRANSFER_UDNIE (XNNPACK FP32) | 1192 | 1025 | -| STYLE_TRANSFER_UDNIE (XNNPACK INT8) | 272 | 430 | -| STYLE_TRANSFER_UDNIE (Core ML FP32) | 100 | - | -| STYLE_TRANSFER_UDNIE (Core ML FP16) | 150 | - | -| STYLE_TRANSFER_RAIN_PRINCESS (XNNPACK FP32) | 1192 | 1025 | -| STYLE_TRANSFER_RAIN_PRINCESS (XNNPACK INT8) | 272 | 430 | -| STYLE_TRANSFER_RAIN_PRINCESS (Core ML FP32) | 100 | - | -| STYLE_TRANSFER_RAIN_PRINCESS (Core ML FP16) | 150 | - | - -## OCR - -Notice that the recognizer models were executed between 3 and 7 times during a single recognition. -The values below represent the averages across all runs for the benchmark image. - -| Model | iPhone 17 Pro [ms] | iPhone 16 Pro [ms] | iPhone SE 3 | Samsung Galaxy S24 [ms] | OnePlus 12 [ms] | -| ------------------------------- | ------------------ | ------------------ | ----------- | ----------------------- | --------------- | -| **Total Inference Time** | 652 | 600 | 2855 | 1092 | 1034 | -| Detector (CRAFT) `forward_800` | 220 | 221 | 1740 | 521 | 492 | -| Recognizer (CRNN) `forward_512` | 45 | 38 | 110 | 40 | 38 | -| Recognizer (CRNN) `forward_256` | 21 | 18 | 54 | 20 | 19 | -| Recognizer (CRNN) `forward_128` | 11 | 9 | 27 | 10 | 10 | - -## Vertical OCR - -:::note -Recognizer models, as well as detector's `forward_320` method, were executed between 4 and 21 times during a single recognition. -::: -The values below represent the averages across all runs for the benchmark image. - -| Model | iPhone 17 Pro
[ms] | iPhone 16 Pro
[ms] | iPhone SE 3 | Samsung Galaxy S24
[ms] | OnePlus 12
[ms] | -| ------------------------------- | ------------------------- | ------------------------- | ----------- | ------------------------------ | ---------------------- | -| **Total Inference Time** | 1104 | 1113 | 8840 | 2845 | 2640 | -| Detector (CRAFT) `forward_1280` | 501 | 507 | 4317 | 1405 | 1275 | -| Detector (CRAFT) `forward_320` | 125 | 121 | 1060 | 338 | 299 | -| Recognizer (CRNN) `forward_512` | 46 | 42 | 109 | 47 | 37 | -| Recognizer (CRNN) `forward_64` | 5 | 6 | 14 | 7 | 6 | - -## LLMs - -| Model | Google Pixel 10 (XNNPACK) [tokens/s] | iPhone 17 Pro (XNNPACK) [tokens/s] | OnePlus 12 (XNNPACK) [tokens/s] | iPhone SE 3 (XNNPACK) [tokens/s] | -| ------------------------------ | :----------------------------------: | :--------------------------------: | :-----------------------------: | :------------------------------: | -| LLAMA3_2_1B | 8 | 8 | 15 | N/A | -| LLAMA3_2_1B_QLORA | 22 | 22 | 45 | 19 | -| LLAMA3_2_1B_SPINQUANT | 24 | 36 | 48 | 17 | -| LLAMA3_2_3B | 2 | 3 | 6 | N/A | -| LLAMA3_2_3B_QLORA | 8 | 7 | 17 | N/A | -| LLAMA3_2_3B_SPINQUANT | 11 | 12 | 18 | N/A | -| QWEN3_0_6B | 7 | 9 | 15 | 9 | -| QWEN3_0_6B_QUANTIZED | 20 | 27 | 37 | 35 | -| QWEN3_1_7B | 3 | 5 | 8 | N/A | -| QWEN3_1_7B_QUANTIZED | 10 | 14 | 20 | 13 | -| QWEN3_4B | 2 | N/A | 4 | N/A | -| QWEN3_4B_QUANTIZED | 5 | 7 | 10 | N/A | -| HAMMER2_1_0_5B | 13 | 13 | 25 | 16 | -| HAMMER2_1_0_5B_QUANTIZED | 34 | 97 | 72 | 56 | -| HAMMER2_1_1_5B | 5 | 5 | 10 | N/A | -| HAMMER2_1_1_5B_QUANTIZED | 14 | 16 | 36 | 22 | -| HAMMER2_1_3B | 2 | 3 | 5 | N/A | -| HAMMER2_1_3B_QUANTIZED | 9 | 10 | 20 | N/A | -| SMOLLM2_1_135M | 25 | 24 | 33 | 42 | -| SMOLLM2_1_135M_QUANTIZED | 20 | 32 | 64 | 47 | -| SMOLLM2_1_360M | 12 | 13 | 20 | 15 | -| SMOLLM2_1_360M_QUANTIZED | 12 | 15 | 29 | 18 | -| SMOLLM2_1_1_7B | 3 | 5 | 7 | N/A | -| SMOLLM2_1_1_7B_QUANTIZED | 12 | 14 | 27 | 23 | -| QWEN2_5_0_5B | 12 | 12 | 21 | 15 | -| QWEN2_5_0_5B_QUANTIZED | 33 | 31 | 55 | 48 | -| QWEN2_5_1_5B | 5 | 5 | 9 | N/A | -| QWEN2_5_1_5B_QUANTIZED | 15 | 15 | 28 | 16 | -| QWEN2_5_3B | 2 | 3 | 5 | N/A | -| QWEN2_5_3B_QUANTIZED | 9 | 10 | 18 | N/A | -| PHI_4_MINI_4B | 2 | 3 | 4 | N/A | -| PHI_4_MINI_4B_QUANTIZED | 4 | 7 | 10 | N/A | -| LFM2_5_350M | 16 | 26 | 34 | 21 | -| LFM2_5_350M_QUANTIZED | 58 | 67 | 103 | 51 | -| LFM2_5_1_2B_INSTRUCT | 6 | 10 | 13 | N/A | -| LFM2_5_1_2B_INSTRUCT_QUANTIZED | 8 | 26 | 47 | 24 | - -❌ - Insufficient RAM. - -## Speech to Text - -### Encoding - -Average time for encoding audio of given length over 10 runs. For `Whisper` model we only list 30 sec audio chunks since `Whisper` does not accept other lengths (for shorter audio the audio needs to be padded to 30sec with silence). - -| Model | iPhone 17 Pro (XNNPACK) [ms] | iPhone 16 Pro (XNNPACK) [ms] | iPhone SE 3 (XNNPACK) [ms] | Samsung Galaxy S24 (XNNPACK) [ms] | OnePlus 12 (XNNPACK) [ms] | -| ------------------ | :--------------------------: | :--------------------------: | :------------------------: | :-------------------------------: | :-----------------------: | -| Whisper-tiny (30s) | 89 | 93 | 403 | 277 | 260 | - -### Decoding - -Average time for decoding one token in sequence of approximately 100 tokens, with encoding context is obtained from audio of noted length. - -| Model | iPhone 17 Pro (XNNPACK) [ms] | iPhone 16 Pro (XNNPACK) [ms] | iPhone SE 3 (XNNPACK) [ms] | Samsung Galaxy S24 (XNNPACK) [ms] | OnePlus 12 (XNNPACK) [ms] | -| ------------------ | :--------------------------: | :--------------------------: | :------------------------: | :-------------------------------: | :-----------------------: | -| Whisper-tiny (30s) | 6 | 6 | 40 | 28 | 25 | - -## Text to Speech - -Average time to synthesize speech from an input text of approximately 60 tokens, resulting in 2 to 5 seconds of audio depending on the input and selected voice. - -| Model | iPhone 17 Pro (XNNPACK) [ms] | OnePlus 12 (XNNPACK) [ms] | -| ------------- | :--------------------------: | :-----------------------: | -| Kokoro-small | 2051 | 1548 | -| Kokoro-medium | 2124 | 1625 | - -## Text Embeddings - -:::note -Benchmark times for text embeddings are highly dependent on the sentence length. The numbers below are based on a sentence of around 80 tokens. For shorter or longer sentences, inference time may vary accordingly. -::: - -| Model / Device | iPhone 17 Pro [ms] | OnePlus 12 [ms] | -| ----------------------------------------------------- | :----------------: | :-------------: | -| ALL_MINILM_L6_V2 (XNNPACK) | 7 | 21 | -| ALL_MPNET_BASE_V2 (XNNPACK) | 24 | 90 | -| MULTI_QA_MINILM_L6_COS_V1 (XNNPACK) | 7 | 19 | -| MULTI_QA_MPNET_BASE_DOT_V1 (XNNPACK) | 24 | 88 | -| CLIP_VIT_BASE_PATCH32_TEXT (XNNPACK) | 14 | 39 | -| DISTILUSE_BASE_MULTILINGUAL_CASED_V2 (XNNPACK 8da4w) | 16 | 15 | -| DISTILUSE_BASE_MULTILINGUAL_CASED_V2 (Core ML FP32) | 15 | - | -| PARAPHRASE_MULTILINGUAL_MINILM_L12_V2 (XNNPACK 8da4w) | 14 | 15 | - -## Image Embeddings - -:::note -For this model all input images, whether larger or smaller, are resized before -processing. Resizing is typically fast for small images but may be noticeably -slower for very large images, which can increase total time. -::: - -| Model / Device | iPhone 17 Pro [ms] | Google Pixel 10 [ms] | -| :----------------------------------------- | :----------------: | :------------------: | -| CLIP_VIT_BASE_PATCH32_IMAGE (XNNPACK FP32) | 14 | 68 | -| CLIP_VIT_BASE_PATCH32_IMAGE (XNNPACK INT8) | 11 | 31 | - -## Semantic Segmentation - -:::note -For this model all input images, whether larger or smaller, are resized before -processing. Resizing is typically fast for small images but may be noticeably -slower for very large images, which can increase total time. -::: - -| Model / Device | iPhone 17 Pro [ms] | Google Pixel 10 [ms] | -| :------------------------------------------- | :----------------: | :------------------: | -| DEEPLAB_V3_RESNET50 (XNNPACK FP32) | 2000 | 2200 | -| DEEPLAB_V3_RESNET50 (XNNPACK INT8) | 118 | 380 | -| DEEPLAB_V3_RESNET101 (XNNPACK FP32) | 2900 | 3300 | -| DEEPLAB_V3_RESNET101 (XNNPACK INT8) | 174 | 660 | -| DEEPLAB_V3_MOBILENET_V3_LARGE (XNNPACK FP32) | 131 | 153 | -| DEEPLAB_V3_MOBILENET_V3_LARGE (XNNPACK INT8) | 17 | 40 | -| LRASPP_MOBILENET_V3_LARGE (XNNPACK FP32) | 13 | 36 | -| LRASPP_MOBILENET_V3_LARGE (XNNPACK INT8) | 12 | 20 | -| FCN_RESNET50 (XNNPACK FP32) | 1800 | 2160 | -| FCN_RESNET50 (XNNPACK INT8) | 100 | 320 | -| FCN_RESNET101 (XNNPACK FP32) | 2600 | 3160 | -| FCN_RESNET101 (XNNPACK INT8) | 160 | 620 | - -## Instance Segmentation - -:::note -Times presented in the tables are measured for YOLO models with input size equal -to 512. Other input sizes may yield slower or faster inference times. RF-DETR -Nano Seg uses a fixed resolution of 312×312. -::: - -| Model | Samsung Galaxy S24 [ms] | Iphone 17 pro [ms] | Pixel 10 [ms] | -| :------------------------- | :---------------------: | :----------------: | :-----------: | -| YOLO26N_SEG (XNNPACK) | 92 | 90 | 93 | -| YOLO26S_SEG (XNNPACK) | 220 | 188 | 193 | -| YOLO26M_SEG (XNNPACK) | 570 | 550 | 481 | -| YOLO26L_SEG (XNNPACK) | 680 | 608 | 582 | -| YOLO26X_SEG (XNNPACK) | 1410 | 1338 | 1191 | -| RF_DETR_NANO_SEG (XNNPACK) | 549 | 330 | 428 | -| FASTSAM_S (XNNPACK) | 184 | 30 | 286 | -| FASTSAM_X (XNNPACK) | 1886 | 2520 | 1993 | -| FASTSAM_S (Core ML) | - | 51 | - | -| FASTSAM_X (Core ML) | - | 72 | - | - -## Text to image - -| Model | iPhone 17 Pro (XNNPACK) [ms] | iPhone 16 Pro (XNNPACK) [ms] | iPhone SE 3 (XNNPACK) [ms] | Samsung Galaxy S24 (XNNPACK) [ms] | OnePlus 12 (XNNPACK) [ms] | -| --------------------- | :--------------------------: | :--------------------------: | :------------------------: | :-------------------------------: | :-----------------------: | -| BK_SDM_TINY_VPRED_256 | 21184 | 21021 | ❌ | 18834 | 16617 | diff --git a/docs/docs/02-benchmarks/memory-usage.md b/docs/docs/02-benchmarks/memory-usage.md deleted file mode 100644 index c685955061..0000000000 --- a/docs/docs/02-benchmarks/memory-usage.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -title: Memory Usage ---- - -:::info -Memory usage values represent the peak memory increase observed while the model was -loaded and actively running inference, relative to the baseline app memory -before model initialization. -::: - -## Classification - -| Model / Device | iPhone 17 Pro [MB] | Google Pixel 10 [MB] | -| -------------------------------- | :----------------: | :------------------: | -| EFFICIENTNET_V2_S (XNNPACK FP32) | 101 | 122 | -| EFFICIENTNET_V2_S (XNNPACK INT8) | 62 | 78 | -| EFFICIENTNET_V2_S (Core ML FP32) | 101 | - | -| EFFICIENTNET_V2_S (Core ML FP16) | 87 | - | - -## Object Detection - -:::note -Data presented for YOLO models is based on inference with `forward_640` method. -::: - -| Model / Device | iPhone 17 Pro [MB] | Google Pixel 10 [MB] | -| --------------------------------------------- | :----------------: | :------------------: | -| SSDLITE_320_MOBILENET_V3_LARGE (XNNPACK FP32) | 94 | 104 | -| SSDLITE_320_MOBILENET_V3_LARGE (Core ML FP32) | 83 | - | -| SSDLITE_320_MOBILENET_V3_LARGE (Core ML FP16) | 62 | - | -| RF_DETR_NANO (XNNPACK FP32) | 145 | 162 | -| YOLO26N (XNNPACK FP32) | 36 | 44 | -| YOLO26S (XNNPACK FP32) | 81 | 82 | -| YOLO26M (XNNPACK FP32) | 123 | 158 | -| YOLO26L (XNNPACK FP32) | 170 | 172 | -| YOLO26X (XNNPACK FP32) | 320 | 309 | - -## Style Transfer - -| Model / Device | iPhone 17 Pro [MB] | Google Pixel 10 [MB] | -| ------------------------------------------- | :----------------: | :------------------: | -| STYLE_TRANSFER_CANDY (XNNPACK FP32) | 1200 | 1200 | -| STYLE_TRANSFER_CANDY (XNNPACK INT8) | 800 | 800 | -| STYLE_TRANSFER_CANDY (Core ML FP32) | 400 | - | -| STYLE_TRANSFER_CANDY (Core ML FP16) | 380 | - | -| STYLE_TRANSFER_MOSAIC (XNNPACK FP32) | 1200 | 1200 | -| STYLE_TRANSFER_MOSAIC (XNNPACK INT8) | 800 | 800 | -| STYLE_TRANSFER_MOSAIC (Core ML FP32) | 400 | - | -| STYLE_TRANSFER_MOSAIC (Core ML FP16) | 380 | - | -| STYLE_TRANSFER_UDNIE (XNNPACK FP32) | 1200 | 1200 | -| STYLE_TRANSFER_UDNIE (XNNPACK INT8) | 800 | 800 | -| STYLE_TRANSFER_UDNIE (Core ML FP32) | 400 | - | -| STYLE_TRANSFER_UDNIE (Core ML FP16) | 380 | - | -| STYLE_TRANSFER_RAIN_PRINCESS (XNNPACK FP32) | 1200 | 1200 | -| STYLE_TRANSFER_RAIN_PRINCESS (XNNPACK INT8) | 800 | 800 | -| STYLE_TRANSFER_RAIN_PRINCESS (Core ML FP32) | 400 | - | -| STYLE_TRANSFER_RAIN_PRINCESS (Core ML FP16) | 380 | - | - -## OCR - -| Model / Device | iPhone 17 Pro [MB] | OnePlus 12 [MB] | -| --------------------------------------------------- | :----------------: | :-------------: | -| Detector (CRAFT) + Recognizer (CRNN) (XNNPACK FP32) | 1320 | 1400 | - -## Vertical OCR - -| Model / Device | iPhone 17 Pro [MB] | OnePlus 12 [MB] | -| --------------------------------------------------- | :----------------: | :-------------: | -| Detector (CRAFT) + Recognizer (CRNN) (XNNPACK FP32) | 1000-1500 | 1000-1600 | - -## LLMs - -| Model / Device | iPhone 17 Pro [GB] | OnePlus 12 [GB] | -| ------------------------------- | :----------------: | :-------------: | -| LLAMA3_2_1B (XNNPACK) | 3.1 | 3.3 | -| LLAMA3_2_1B_SPINQUANT (XNNPACK) | 2.4 | 1.9 | -| LLAMA3_2_1B_QLORA (XNNPACK) | 2.8 | 2.7 | -| LLAMA3_2_3B (XNNPACK) | 7.3 | 7.1 | -| LLAMA3_2_3B_SPINQUANT (XNNPACK) | 3.8 | 3.7 | -| LLAMA3_2_3B_QLORA (XNNPACK) | 4.0 | 3.9 | - -## Speech to Text - -| Model / Device | iPhone 17 Pro [MB] | OnePlus 12 [MB] | -| ---------------------- | :----------------: | :-------------: | -| WHISPER_TINY (XNNPACK) | 375 | 410 | - -## Text to Speech - -:::note -The reported memory usage values include the memory footprint of the Phonemis package, which is used for phonemizing input text. Currently, this can range from 100 to 150 MB depending on the device. -::: - -| Model / Device | iPhone 17 Pro [MB] | OnePlus 12 [MB] | -| ----------------------- | :----------------: | :-------------: | -| KOKORO_SMALL (XNNPACK) | 820 | 820 | -| KOKORO_MEDIUM (XNNPACK) | 1100 | 1140 | - -## Text Embeddings - -| Model / Device | iPhone 17 Pro [MB] | OnePlus 12 [MB] | -| ----------------------------------------------------- | :----------------: | :-------------: | -| ALL_MINILM_L6_V2 (XNNPACK) | 110 | 95 | -| ALL_MPNET_BASE_V2 (XNNPACK) | 455 | 405 | -| MULTI_QA_MINILM_L6_COS_V1 (XNNPACK) | 140 | 120 | -| MULTI_QA_MPNET_BASE_DOT_V1 (XNNPACK) | 455 | 435 | -| CLIP_VIT_BASE_PATCH32_TEXT (XNNPACK) | 280 | 200 | -| DISTILUSE_BASE_MULTILINGUAL_CASED_V2 (XNNPACK 8da4w) | 36 | 44 | -| DISTILUSE_BASE_MULTILINGUAL_CASED_V2 (Core ML FP32) | 55 | - | -| PARAPHRASE_MULTILINGUAL_MINILM_L12_V2 (XNNPACK 8da4w) | 131 | 141 | - -## Image Embeddings - -| Model / Device | iPhone 17 Pro [MB] | Google Pixel 10 [MB] | -| ------------------------------------------ | :----------------: | :------------------: | -| CLIP_VIT_BASE_PATCH32_IMAGE (XNNPACK FP32) | 340 | 345 | - -## Semantic Segmentation - -:::note -Data presented in the following sections is based on inference with non-resized -output. When resize is enabled, expect higher memory usage and inference time -with higher resolutions. -::: - -| Model / Device | iPhone 17 Pro [MB] | OnePlus 12 [MB] | -| ---------------------------- | :----------------: | :-------------: | -| DEEPLABV3_RESNET50 (XNNPACK) | 660 | 930 | - -## Instance Segmentation - -:::note -Data presented in the following sections is based on inference with 640x640 input. -::: - -| Model / Device | iPhone 17 Pro [MB] | OnePlus 12 [MB] | -| -------------------------- | :----------------: | :-------------: | -| YOLO26N_SEG (XNNPACK) | 668 | 92 | -| YOLO26S_SEG (XNNPACK) | 712 | 220 | -| YOLO26M_SEG (XNNPACK) | 815 | 570 | -| YOLO26L_SEG (XNNPACK) | 1024 | 680 | -| YOLO26X_SEG (XNNPACK) | 1450 | 1410 | -| RF_DETR_NANO_SEG (XNNPACK) | 603 | 620 | -| FASTSAM_S (XNNPACK FP32) | 535 | 567 | -| FASTSAM_X (XNNPACK FP32) | 760 | 792 | -| FASTSAM_S (Core ML FP16) | 523 | - | -| FASTSAM_X (Core ML FP16) | 684 | - | - -## Text to Image - -| Model / Device | iPhone 17 Pro [MB] | OnePlus 12 [MB] | -| ------------------------------- | :----------------: | :-------------: | -| BK_SDM_TINY_VPRED_256 (XNNPACK) | 2400 | 2400 | -| BK_SDM_TINY_VPRED (XNNPACK) | 6050 | 6210 | diff --git a/docs/docs/02-benchmarks/model-size.md b/docs/docs/02-benchmarks/model-size.md deleted file mode 100644 index cfa58823e1..0000000000 --- a/docs/docs/02-benchmarks/model-size.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -title: Model Size ---- - -## Classification - -| Model | XNNPACK FP32 [MB] | XNNPACK INT8 [MB] | Core ML FP32 [MB] | Core ML FP16 [MB] | -| :---------------- | :---------------: | :---------------: | :---------------: | :---------------: | -| EFFICIENTNET_V2_S | 85.7 | 22.9 | 86.5 | 43.9 | - -## Object Detection - -| Model | XNNPACK FP32 [MB] | Core ML FP32 [MB] | Core ML FP16 [MB] | -| ------------------------------ | :---------------: | :---------------: | :---------------: | -| SSDLITE_320_MOBILENET_V3_LARGE | 13.9 | 15.6 | 8.46 | -| RF_DETR_NANO | 112 | - | - | -| YOLO26N | 10.3 | - | - | -| YOLO26S | 38.6 | - | - | -| YOLO26M | 82.3 | - | - | -| YOLO26L | 100 | - | - | -| YOLO26X | 224 | - | - | - -## Instance Segmentation - -| Model | XNNPACK [MB] | Core ML FP32 [MB] | Core ML FP16 [MB] | -| ---------------- | :----------: | :---------------: | :---------------: | -| YOLO26N_SEG | 11.6 | - | - | -| YOLO26S_SEG | 42.3 | - | - | -| YOLO26M_SEG | 95.4 | - | - | -| YOLO26L_SEG | 113 | - | - | -| YOLO26X_SEG | 252 | - | - | -| RF_DETR_NANO_SEG | 124 | - | - | -| FASTSAM_S | 47.3 | 47.8 | 24.2 | -| FASTSAM_X | 289 | 290 | 145 | - -## Style Transfer - -| Model | XNNPACK FP32 [MB] | XNNPACK INT8 [MB] | Core ML FP32 [MB] | Core ML FP16 [MB] | -| ---------------------------- | :---------------: | :---------------: | :---------------: | :---------------: | -| STYLE_TRANSFER_CANDY | 6.82 | 1.84 | 7.12 | 3.79 | -| STYLE_TRANSFER_MOSAIC | 6.82 | 1.84 | 7.12 | 3.79 | -| STYLE_TRANSFER_UDNIE | 6.82 | 1.84 | 7.12 | 3.79 | -| STYLE_TRANSFER_RAIN_PRINCESS | 6.82 | 1.84 | 7.12 | 3.79 | - -## OCR - -| Model | XNNPACK [MB] | -| -------------------------- | :-----------: | -| Detector (CRAFT_QUANTIZED) | 20.9 | -| Recognizer (CRNN) | 18.5 - 25.2\* | - -\* - The model weights vary depending on the language. - -## Vertical OCR - -| Model | XNNPACK [MB] | -| -------------------------- | :-----------: | -| Detector (CRAFT_QUANTIZED) | 20.9 | -| Recognizer (CRNN) | 18.5 - 25.2\* | - -\* - The model weights vary depending on the language. - -## LLMs - -| Model | XNNPACK [GB] | -| ------------------------------ | :----------: | -| LLAMA3_2_1B | 2.47 | -| LLAMA3_2_1B_SPINQUANT | 1.14 | -| LLAMA3_2_1B_QLORA | 1.18 | -| LLAMA3_2_3B | 6.43 | -| LLAMA3_2_3B_SPINQUANT | 2.55 | -| LLAMA3_2_3B_QLORA | 2.65 | -| QWEN3_0.6B | 1.11 | -| QWEN3_0.6B_QUANTIZED | 0.47 | -| QWEN3_1.7B | 3.21 | -| QWEN3_1.7B_QUANTIZED | 1.21 | -| QWEN3_4B | 7.49 | -| QWEN3_4B_QUANTIZED | 2.50 | -| QWEN2_5_0.5B | 0.92 | -| QWEN2_5_0.5B_QUANTIZED | 0.39 | -| QWEN2_5_1.5B | 2.88 | -| QWEN2_5_1.5B_QUANTIZED | 1.06 | -| QWEN2_5_3B | 5.75 | -| QWEN2_5_3B_QUANTIZED | 1.95 | -| HAMMER2_1_0.5B | 0.92 | -| HAMMER2_1_0.5B_QUANTIZED | 0.39 | -| HAMMER2_1_1.5B | 2.88 | -| HAMMER2_1_1.5B_QUANTIZED | 1.06 | -| HAMMER2_1_3B | 5.75 | -| HAMMER2_1_3B_QUANTIZED | 1.91 | -| PHI4_MINI | 7.15 | -| PHI4_MINI_QUANTIZED | 2.62 | -| SMOLLM2_135M | 0.25 | -| SMOLLM2_135M_QUANTIZED | 0.52 | -| SMOLLM2_360M | 0.67 | -| SMOLLM2_360M_QUANTIZED | 1.27 | -| SMOLLM2_1.7B | 3.19 | -| SMOLLM2_1.7B_QUANTIZED | 0.95 | -| LFM2_5_1.2B_INSTRUCT | 2.43 | -| LFM2_5_1.2B_INSTRUCT_QUANTIZED | 0.74 | -| LFM2_5_350M_FP16 | 0.79 | -| LFM2_5_350M_QUANTIZED | 0.26 | - -## Speech to text - -| Model | XNNPACK [MB] | -| ---------------- | :----------: | -| WHISPER_TINY_EN | 151 | -| WHISPER_TINY | 151 | -| WHISPER_BASE_EN | 290.6 | -| WHISPER_BASE | 290.6 | -| WHISPER_SMALL_EN | 968 | -| WHISPER_SMALL | 968 | - -## Text to speech - -| Model | XNNPACK [MB] | -| ------------- | :----------: | -| KOKORO_SMALL | 329.6 | -| KOKORO_MEDIUM | 334.4 | - -## Text Embeddings - -| Model | Size [MB] | -| ----------------------------------------------- | :-------: | -| ALL_MINILM_L6_V2 | 91 | -| ALL_MPNET_BASE_V2 | 438 | -| MULTI_QA_MINILM_L6_COS_V1 | 91 | -| MULTI_QA_MPNET_BASE_DOT_V1 | 438 | -| CLIP_VIT_BASE_PATCH32_TEXT | 254 | -| DISTILUSE_BASE_MULTILINGUAL_CASED_V2_8DA4W | 393 | -| PARAPHRASE_MULTILINGUAL_MINILM_L12_V2_QUANTIZED | 397 | - -## Image Embeddings - -| Model | XNNPACK FP32 [MB] | XNNPACK INT8 [MB] | -| --------------------------- | :---------------: | :---------------: | -| CLIP_VIT_BASE_PATCH32_IMAGE | 352 | 96.4 | - -## Semantic Segmentation - -| Model | XNNPACK FP32 [MB] | XNNPACK INT8 [MB] | -| ----------------------------- | :---------------: | :---------------: | -| DEEPLAB_V3_RESNET50 | 168 | 42.4 | -| DEEPLAB_V3_RESNET101 | 244 | 61.7 | -| DEEPLAB_V3_MOBILENET_V3_LARGE | 44.1 | 11.4 | -| LRASPP_MOBILENET_V3_LARGE | 12.9 | 3.53 | -| FCN_RESNET50 | 141 | 35.7 | -| FCN_RESNET101 | 217 | 55 | - -## Text to image - -| Model | Text encoder (XNNPACK) [MB] | UNet (XNNPACK) [MB] | VAE decoder (XNNPACK) [MB] | -| ----------------- | --------------------------- | ------------------- | -------------------------- | -| BK_SDM_TINY_VPRED | 492 | 1290 | 198 | - -## Voice Activity Detection (VAD) - -| Model | XNNPACK [MB] | -| -------- | :----------: | -| FSMN_VAD | 1.83 | diff --git a/docs/docs/03-hooks/01-natural-language-processing/_category_.json b/docs/docs/03-hooks/01-natural-language-processing/_category_.json deleted file mode 100644 index 0314f315d2..0000000000 --- a/docs/docs/03-hooks/01-natural-language-processing/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Natural Language Processing", - "link": { - "type": "generated-index" - } -} diff --git a/docs/docs/03-hooks/01-natural-language-processing/useLLM.md b/docs/docs/03-hooks/01-natural-language-processing/useLLM.md deleted file mode 100644 index 29b1be4d72..0000000000 --- a/docs/docs/03-hooks/01-natural-language-processing/useLLM.md +++ /dev/null @@ -1,580 +0,0 @@ ---- -title: useLLM -keywords: - [ - react native, - react native ai, - react native llm, - react native qwen, - react native llama, - react native executorch, - executorch, - pytorch, - on-device ai, - mobile ai, - llama 3, - qwen, - text generation, - tool calling, - function calling, - ] -description: "Learn how to use LLMs in your React Native applications with React Native ExecuTorch's useLLM hook." ---- - -React Native ExecuTorch supports a variety of LLMs (checkout our [HuggingFace repository](https://huggingface.co/software-mansion) for model already converted to ExecuTorch format) including Llama 3.2. Before getting started, you’ll need to obtain the .pte binary—a serialized model, the tokenizer and tokenizer config JSON files. There are various ways to accomplish this: - -:::info -It is recommended to use models provided by us, which are available at our [HuggingFace repository](https://huggingface.co/collections/software-mansion/llm). You can also use [constants](../../06-api-reference/index.md#models---llm) shipped with our library. - -Alternatively, follow the official [tutorial](https://docs.pytorch.org/executorch/stable/llm/export-llm.html) made by ExecuTorch team to export an arbitrary LLM model. -::: - -:::warning -Lower-end devices might not be able to fit LLMs into memory. We recommend using quantized models to reduce the memory footprint. -::: - -## API Reference - -- For detailed API Reference for `useLLM` see: [`useLLM` API Reference](../../06-api-reference/functions/useLLM.md). -- For all LLM models available out-of-the-box in React Native ExecuTorch see: [LLM Models](../../06-api-reference/index.md#models---llm). -- For useful LLM utility functionalities please refer to the following link: [LLM Utility Functionalities](../../06-api-reference/index.md#utilities---llm). - -## Initializing - -In order to load a model into the app, you need to run the following code: - -```typescript -import { models, useLLM } from 'react-native-executorch'; -const llm = useLLM({ model: models.llm.lfm2_5_1_2b_instruct() }); -``` - -
- -The code snippet above fetches the model from the specified URL, loads it into memory, and returns an object with various functions and properties for controlling the model. You can monitor the loading progress by checking the [`llm.downloadProgress`](../../06-api-reference/interfaces/LLMType.md#downloadprogress) and [`llm.isReady`](../../06-api-reference/interfaces/LLMType.md#isready) property, and if anything goes wrong, the [`llm.error`](../../06-api-reference/interfaces/LLMType.md#error) property will contain the error message. - -### Arguments - -`useLLM` takes [`LLMProps`](../../06-api-reference/interfaces/LLMProps.md) that consists of: - -- [model](../../06-api-reference/interfaces/LLMModel.md). -- An optional flag [`preventLoad`](../../06-api-reference/interfaces/SpeechToTextProps.md#preventload) which prevents auto-loading of the model. - -You need more details? Check the following resources: - -- For detailed information about `useLLM` arguments check this section: [`useLLM` arguments](../../06-api-reference/functions/useLLM.md#parameters). -- For more information on loading resources, take a look at [loading models](../../01-fundamentals/02-loading-models.md) page. -- For available LLM models please check out the following list: [LLM Models](../../06-api-reference/index.md#models---llm). - -### Returns - -`useLLM` returns [`LLMType`](../../06-api-reference/interfaces/LLMType.md) which provides: - -- State properties: [`response`](../../06-api-reference/interfaces/LLMType.md#response), [`token`](../../06-api-reference/interfaces/LLMType.md#token), [`isReady`](../../06-api-reference/interfaces/LLMType.md#isready), [`isGenerating`](../../06-api-reference/interfaces/LLMType.md#isgenerating), [`downloadProgress`](../../06-api-reference/interfaces/LLMType.md#downloadprogress), [`error`](../../06-api-reference/interfaces/LLMType.md#error), [`messageHistory`](../../06-api-reference/interfaces/LLMType.md#messagehistory) -- Generation methods: [`generate`](../../06-api-reference/interfaces/LLMType.md#generate), [`sendMessage`](../../06-api-reference/interfaces/LLMType.md#sendmessage), [`interrupt`](../../06-api-reference/interfaces/LLMType.md#interrupt) -- Configuration: [`configure`](../../06-api-reference/interfaces/LLMType.md#configure), [`deleteMessage`](../../06-api-reference/interfaces/LLMType.md#deletemessage) -- Token counting: [`getGeneratedTokenCount`](../../06-api-reference/interfaces/LLMType.md#getgeneratedtokencount), [`getPromptTokenCount`](../../06-api-reference/interfaces/LLMType.md#getprompttokencount), [`getTotalTokenCount`](../../06-api-reference/interfaces/LLMType.md#gettotaltokencount) - -For complete details, see the [LLMType API Reference](../../06-api-reference/interfaces/LLMType.md). - -## Functional vs managed - -You can use functions returned from this hooks in two manners: - -1. Functional/pure - we will not keep any state for you. You'll need to keep conversation history and handle function calling yourself. Use [`generate`](../../06-api-reference/interfaces/LLMType.md#generate) and [`response`](../../06-api-reference/interfaces/LLMType.md#response). Note that you don't need to run [`configure`](../../06-api-reference/interfaces/LLMType.md#configure) to use those. Furthermore, [`chatConfig`](../../06-api-reference/interfaces/LLMConfig.md#chatconfig) and [`toolsConfig`](../../06-api-reference/interfaces/LLMConfig.md#toolsconfig) will not have any effect on those functions. - -2. Managed/stateful - we will manage conversation state. Tool calls will be parsed and called automatically after passing appropriate callbacks. See more at [managed LLM chat](#managed-llm-chat). - -## Functional way - -### Simple generation - -To perform chat completion you can use the [`generate`](../../06-api-reference/interfaces/LLMType.md#generate) function. The [`response`](../../06-api-reference/interfaces/LLMType.md#response) value is updated with each token as it's generated, and the function returns a promise that resolves to the complete response when generation finishes. - -```tsx -const llm = useLLM({ model: models.llm.lfm2_5_1_2b_instruct() }); - -const handleGenerate = async () => { - const chat: Message[] = [ - { role: 'system', content: 'You are a helpful assistant' }, - { role: 'user', content: 'Hi!' }, - { role: 'assistant', content: 'Hi!, how can I help you?' }, - { role: 'user', content: 'What is the meaning of life?' }, - ]; - - // Chat completion - returns the generated response - const response = await llm.generate(chat); - console.log('Complete response:', response); -}; - -return ( - -