From 54d1adb69d2384a28bf62387ebdcbc6887ed8ae9 Mon Sep 17 00:00:00 2001 From: jakmro Date: Mon, 10 Feb 2025 20:06:08 +0100 Subject: [PATCH 1/3] Use expo file system to fetch files, add functionality listing downloaded files and models --- .../com/swmansion/rnexecutorch/ETModule.kt | 17 +- .../java/com/swmansion/rnexecutorch/LLM.kt | 77 +-- .../rnexecutorch/models/BaseModel.kt | 14 +- .../swmansion/rnexecutorch/utils/Fetcher.kt | 295 --------- .../screens/ClassificationScreen.tsx | 5 +- .../screens/ObjectDetectionScreen.tsx | 2 +- .../screens/StyleTransferScreen.tsx | 5 +- ios/RnExecutorch/Classification.mm | 41 +- ios/RnExecutorch/ETModule.mm | 48 +- ios/RnExecutorch/LLM.mm | 144 ++--- ios/RnExecutorch/ObjectDetection.mm | 6 +- ios/RnExecutorch/StyleTransfer.mm | 41 +- ios/RnExecutorch/models/BaseModel.h | 8 +- ios/RnExecutorch/models/BaseModel.mm | 29 +- ios/RnExecutorch/utils/Fetcher.h | 34 - ios/RnExecutorch/utils/Fetcher.mm | 167 ----- ios/RnExecutorch/utils/LargeFileFetcher.h | 13 - ios/RnExecutorch/utils/LargeFileFetcher.mm | 127 ---- package.json | 4 + src/constants/directories.ts | 3 + .../computer_vision/useClassification.ts | 6 +- .../computer_vision/useObjectDetection.ts | 6 +- src/hooks/computer_vision/useStyleTransfer.ts | 6 +- src/hooks/general/useExecutorchModule.ts | 5 +- .../natural_language_processing/useLLM.ts | 32 +- src/{ => hooks}/useModule.ts | 25 +- src/index.tsx | 3 + src/modules/BaseModule.ts | 19 +- .../natural_language_processing/LLMModule.ts | 26 +- src/native/NativeLLM.ts | 1 - src/utils/FetchResource.ts | 82 +++ src/utils/listResources.ts | 12 + yarn.lock | 598 +++++++++++++++++- 33 files changed, 926 insertions(+), 975 deletions(-) delete mode 100644 android/src/main/java/com/swmansion/rnexecutorch/utils/Fetcher.kt delete mode 100644 ios/RnExecutorch/utils/Fetcher.h delete mode 100644 ios/RnExecutorch/utils/Fetcher.mm delete mode 100644 ios/RnExecutorch/utils/LargeFileFetcher.h delete mode 100644 ios/RnExecutorch/utils/LargeFileFetcher.mm create mode 100644 src/constants/directories.ts rename src/{ => hooks}/useModule.ts (79%) create mode 100644 src/utils/FetchResource.ts create mode 100644 src/utils/listResources.ts diff --git a/android/src/main/java/com/swmansion/rnexecutorch/ETModule.kt b/android/src/main/java/com/swmansion/rnexecutorch/ETModule.kt index b3a57f262d..7f1e73fc46 100644 --- a/android/src/main/java/com/swmansion/rnexecutorch/ETModule.kt +++ b/android/src/main/java/com/swmansion/rnexecutorch/ETModule.kt @@ -6,9 +6,9 @@ import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableArray import com.swmansion.rnexecutorch.utils.ArrayUtils import com.swmansion.rnexecutorch.utils.ETError -import com.swmansion.rnexecutorch.utils.Fetcher import com.swmansion.rnexecutorch.utils.TensorUtils import org.pytorch.executorch.Module +import java.net.URL class ETModule(reactContext: ReactApplicationContext) : NativeETModuleSpec(reactContext) { private lateinit var module: Module @@ -18,19 +18,8 @@ class ETModule(reactContext: ReactApplicationContext) : NativeETModuleSpec(react } override fun loadModule(modelSource: String, promise: Promise) { - Fetcher.downloadModel( - reactApplicationContext, - modelSource, - ) { path, error -> - if (error != null) { - promise.reject(error.message!!, ETError.InvalidModelSource.toString()) - return@downloadModel - } - - module = Module.load(path) - promise.resolve(0) - return@downloadModel - } + module = Module.load(URL(modelSource).path) + promise.resolve(0) } override fun loadMethod(methodName: String, promise: Promise) { diff --git a/android/src/main/java/com/swmansion/rnexecutorch/LLM.kt b/android/src/main/java/com/swmansion/rnexecutorch/LLM.kt index 393468b8f1..4ca13ca51c 100644 --- a/android/src/main/java/com/swmansion/rnexecutorch/LLM.kt +++ b/android/src/main/java/com/swmansion/rnexecutorch/LLM.kt @@ -3,14 +3,12 @@ package com.swmansion.rnexecutorch import android.util.Log import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext -import com.swmansion.rnexecutorch.utils.Fetcher -import com.swmansion.rnexecutorch.utils.ProgressResponseBody -import com.swmansion.rnexecutorch.utils.ResourceType import com.swmansion.rnexecutorch.utils.llms.ChatRole import com.swmansion.rnexecutorch.utils.llms.ConversationManager import com.swmansion.rnexecutorch.utils.llms.END_OF_TEXT_TOKEN import org.pytorch.executorch.LlamaCallback import org.pytorch.executorch.LlamaModule +import java.net.URL class LLM(reactContext: ReactApplicationContext) : NativeLLMSpec(reactContext), LlamaCallback { @@ -18,7 +16,6 @@ class LLM(reactContext: ReactApplicationContext) : private var llamaModule: LlamaModule? = null private var tempLlamaResponse = StringBuilder() private lateinit var conversationManager: ConversationManager - private var isFetching = false override fun getName(): String { return NAME @@ -37,37 +34,6 @@ class LLM(reactContext: ReactApplicationContext) : Log.d("rn_executorch", "TPS: $tps") } - private fun updateDownloadProgress(progress: Float) { - emitOnDownloadProgress((progress / 100).toDouble()) - } - - private fun downloadResource( - url: String, - resourceType: ResourceType, - isLargeFile: Boolean = false, - callback: (path: String?, error: Exception?) -> Unit, - ) { - Fetcher.downloadResource( - reactApplicationContext, url, resourceType, isLargeFile, - { path, error -> callback(path, error) }, - object : ProgressResponseBody.ProgressListener { - override fun onProgress(bytesRead: Long, contentLength: Long, done: Boolean) { - val progress = (bytesRead * 100 / contentLength).toFloat() - updateDownloadProgress(progress) - if (done) { - isFetching = false - } - } - }) - } - - private fun initializeLlamaModule(modelPath: String, tokenizerPath: String, promise: Promise) { - llamaModule = LlamaModule(1, modelPath, tokenizerPath, 0.7f) - isFetching = false - this.tempLlamaResponse.clear() - promise.resolve("Model loaded successfully") - } - override fun loadLLM( modelSource: String, tokenizerSource: String, @@ -75,46 +41,13 @@ class LLM(reactContext: ReactApplicationContext) : contextWindowLength: Double, promise: Promise ) { - if (isFetching) { - promise.reject("Model is fetching", "Model is fetching") - return - } - try { this.conversationManager = ConversationManager(contextWindowLength.toInt(), systemPrompt) - - isFetching = true - - downloadResource( - tokenizerSource, - ResourceType.TOKENIZER - ) tokenizerDownload@{ tokenizerPath, error -> - if (error != null) { - promise.reject("Download Error", "Tokenizer download failed: ${error.message}") - isFetching = false - return@tokenizerDownload - } - - downloadResource( - modelSource, - ResourceType.MODEL, - isLargeFile = true - ) modelDownload@{ modelPath, modelError -> - if (modelError != null) { - promise.reject( - "Download Error", - "Model download failed: ${modelError.message}" - ) - isFetching = false - return@modelDownload - } - - initializeLlamaModule(modelPath!!, tokenizerPath!!, promise) - } - } + llamaModule = LlamaModule(1, URL(modelSource).path, URL(tokenizerSource).path, 0.7f) + this.tempLlamaResponse.clear() + promise.resolve("Model loaded successfully") } catch (e: Exception) { - promise.reject("Download Error", e.message) - isFetching = false + promise.reject("Model loading failed", e.message) } } diff --git a/android/src/main/java/com/swmansion/rnexecutorch/models/BaseModel.kt b/android/src/main/java/com/swmansion/rnexecutorch/models/BaseModel.kt index 4ce67bcdef..ae3e8b9f3a 100644 --- a/android/src/main/java/com/swmansion/rnexecutorch/models/BaseModel.kt +++ b/android/src/main/java/com/swmansion/rnexecutorch/models/BaseModel.kt @@ -2,26 +2,16 @@ package com.swmansion.rnexecutorch.models import android.content.Context import com.swmansion.rnexecutorch.utils.ETError -import com.swmansion.rnexecutorch.utils.Fetcher import org.pytorch.executorch.EValue import org.pytorch.executorch.Module -import org.pytorch.executorch.Tensor +import java.net.URL abstract class BaseModel(val context: Context) { protected lateinit var module: Module fun loadModel(modelSource: String) { - Fetcher.downloadModel( - context, - modelSource - ) { path, error -> - if (error != null) { - throw Error(error.message!!) - } - - module = Module.load(path) - } + module = Module.load(URL(modelSource).path) } protected fun forward(input: EValue): Array { diff --git a/android/src/main/java/com/swmansion/rnexecutorch/utils/Fetcher.kt b/android/src/main/java/com/swmansion/rnexecutorch/utils/Fetcher.kt deleted file mode 100644 index deaf787e36..0000000000 --- a/android/src/main/java/com/swmansion/rnexecutorch/utils/Fetcher.kt +++ /dev/null @@ -1,295 +0,0 @@ -package com.swmansion.rnexecutorch.utils - -import android.content.Context -import okhttp3.Call -import okhttp3.Callback -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import okhttp3.Response -import okio.IOException -import java.io.File -import java.io.FileOutputStream -import java.net.URL - -enum class ResourceType { - TOKENIZER, - MODEL, -} - -class Fetcher { - companion object { - private fun saveResponseToFile( - response: Response, - directory: File, - fileName: String, - ): File { - val file = File(directory.path, fileName) - file.outputStream().use { outputStream -> - response.body?.byteStream()?.copyTo(outputStream) - } - return file - } - - private fun getValidExtension(resourceType: ResourceType): String { - return when (resourceType) { - ResourceType.TOKENIZER -> { - "bin" - } - - ResourceType.MODEL -> { - "pte" - } - } - } - - private fun extractFileName(url: URL): String { - if (url.path == "/assets/") { - val pathSegments = url.toString().split('/') - return pathSegments[pathSegments.size - 1].split("?")[0] - } - - return url.path.substringAfterLast('/') - } - - private fun fetchModel( - file: File, - validFile: File, - client: OkHttpClient, - url: URL, - onComplete: (String?, Exception?) -> Unit, - listener: ProgressResponseBody.ProgressListener? = null, - ) { - val request = Request.Builder().url(url).build() - client.newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - onComplete(null, e) - } - - override fun onResponse(call: Call, response: Response) { - if (!response.isSuccessful) { - onComplete(null, Exception("download_error")) - } - - response.body?.let { body -> - val progressBody = listener?.let { ProgressResponseBody(body, it) } - val inputStream = progressBody?.source()?.inputStream() - inputStream?.use { input -> - FileOutputStream(file).use { output -> - val buffer = ByteArray(2048) - var bytesRead: Int - while (input.read(buffer).also { bytesRead = it } != -1) { - output.write(buffer, 0, bytesRead) - } - } - } - - if (file.renameTo(validFile)) { - onComplete(validFile.absolutePath, null) - } else { - onComplete(null, Exception("Failed to move the file to the valid location")) - } - } - } - }) - } - - private fun isUrlPointingToHfRepo(url: URL): Boolean { - val expectedHost = "huggingface.co" - val expectedPathPrefix = "/software-mansion/" - if (url.host != expectedHost) { - return false - } - return url.path.startsWith(expectedPathPrefix) - } - - private fun resolveConfigUrlFromModelUrl(modelUrl: URL): URL { - // Create a new URL using the base URL and append the desired path - val baseUrl = - modelUrl.protocol + "://" + modelUrl.host + modelUrl.path.substringBefore("resolve/") - return URL(baseUrl + "resolve/main/config.json") - } - - private fun sendRequestToUrl( - url: URL, - method: String, - body: RequestBody?, - client: OkHttpClient - ): Response { - val request = Request.Builder() - .url(url) - .method(method, body) - .build() - val response = client.newCall(request).execute() - return response - } - - private fun getIdOfResource( - context: Context, - resourceName: String, - defType: String = "raw" - ): Int { - return context.resources.getIdentifier(resourceName, defType, context.packageName) - } - - private fun getResourceFromAssets( - context: Context, - url: String, - resourceType: ResourceType, - onComplete: (String?, Exception?) -> Unit - ) { - if (!url.contains("://")) { - //The provided file is from react-native assets folder in release mode - val resId = getIdOfResource(context, url) - val resName = context.resources.getResourceEntryName(resId) - val fileExtension = getValidExtension(resourceType) - context.resources.openRawResource(resId).use { inputStream -> - val file = File( - context.filesDir, - "$resName.$fileExtension" - ) - file.outputStream().use { outputStream -> - inputStream.copyTo(outputStream) - } - onComplete(file.absolutePath, null) - return - } - } - } - - private fun getLocalFile( - url: URL, - resourceType: ResourceType, - onComplete: (String?, Exception?) -> Unit - ) { - // The provided file is a local file, get rid of the file:// prefix and return path - if (url.protocol == "file") { - val localPath = url.path - if (getValidExtension(resourceType) != localPath.takeLast(3)) { - throw Exception("invalid_extension") - } - - val file = File(localPath) - if (file.exists()) { - onComplete(localPath, null) - return - } - - throw Exception("file_not_found") - } - } - - private fun getRemoteFile( - context: Context, - url: URL, - resourceType: ResourceType, - isLargeFile: Boolean, - onComplete: (String?, Exception?) -> Unit, - listener: ProgressResponseBody.ProgressListener? - ) { - val client = OkHttpClientSingleton.instance - val fileName = extractFileName(url) - - if (getValidExtension(resourceType) != fileName.takeLast(3)) { - throw Exception("invalid_extension") - } - - val modelsDirectory = File(context.filesDir, "models").apply { - if (!exists()) { - mkdirs() - } - } - - var validFile = File(modelsDirectory, fileName) - if (validFile.exists()) { - onComplete(validFile.absolutePath, null) - return - } - - // If the url is a Software Mansion HuggingFace repo, we want to send a HEAD - // request to the config.json file, this increments HF download counter - // https://huggingface.co/docs/hub/models-download-stats - if (isUrlPointingToHfRepo(url)) { - val configUrl = resolveConfigUrlFromModelUrl(url) - sendRequestToUrl(configUrl, "HEAD", null, client) - } - - if (!isLargeFile) { - val request = Request.Builder().url(url).build() - val response = client.newCall(request).execute() - - if (!response.isSuccessful) { - throw Exception("download_error") - } - - validFile = saveResponseToFile(response, modelsDirectory, fileName) - onComplete(validFile.absolutePath, null) - return - } - - val tempFile = File(context.filesDir, fileName) - if (tempFile.exists()) { - tempFile.delete() - } - - fetchModel(tempFile, validFile, client, url, onComplete, listener) - } - - fun downloadModel(context: Context, url: String, callback: (String?, Exception?) -> Unit) { - downloadResource(context, - url, - ResourceType.MODEL, - false, - { path, error -> callback(path, error) }) - } - - fun downloadResource( - context: Context, - url: String, - resourceType: ResourceType, - isLargeFile: Boolean, - onComplete: (String?, Exception?) -> Unit, - listener: ProgressResponseBody.ProgressListener? = null, - ) { - /* - Fetching model and tokenizer file - 1. Check if the provided file is a bundled local file - a. Check if it exists - b. Check if it has valid extension - c. Copy the file and return the path - 2. Check if the provided file is a path to a local file - a. Check if it exists - b. Check if it has valid extension - c. Return the path - 3. The provided file is a remote file - a. Check if it has valid extension - b. Check if it's a large file - i. Create temporary file to store it at download time - ii. Move it to the models directory and return the path - c. If it's not a large file download it and return the path - */ - - try { - getResourceFromAssets(context, url, resourceType, onComplete) - - val resUrl = URL(url) - /* - The provided file is either a remote file or a local file - - local file: file:///path/to/file - - remote file: https://path/to/file || http://10.0.2.2:8080/path/to/file - */ - getLocalFile(resUrl, resourceType, onComplete) - - /* - The provided file is a remote file, if it's a large file - create temporary file to store it at download time and later - move it to the models directory - */ - getRemoteFile(context, resUrl, resourceType, isLargeFile, onComplete, listener) - } catch (e: Exception) { - onComplete(null, e) - return - } - } - } -} diff --git a/examples/computer-vision/screens/ClassificationScreen.tsx b/examples/computer-vision/screens/ClassificationScreen.tsx index 5c3b0c3c94..5f6b31b0ab 100644 --- a/examples/computer-vision/screens/ClassificationScreen.tsx +++ b/examples/computer-vision/screens/ClassificationScreen.tsx @@ -46,7 +46,10 @@ export const ClassificationScreen = ({ if (!model.isReady) { return ( - + ); } diff --git a/examples/computer-vision/screens/ObjectDetectionScreen.tsx b/examples/computer-vision/screens/ObjectDetectionScreen.tsx index ea82dfd493..eea471d1fe 100644 --- a/examples/computer-vision/screens/ObjectDetectionScreen.tsx +++ b/examples/computer-vision/screens/ObjectDetectionScreen.tsx @@ -56,7 +56,7 @@ export const ObjectDetectionScreen = ({ return ( ); } diff --git a/examples/computer-vision/screens/StyleTransferScreen.tsx b/examples/computer-vision/screens/StyleTransferScreen.tsx index 8ccd0a8deb..0f6b5c9a52 100644 --- a/examples/computer-vision/screens/StyleTransferScreen.tsx +++ b/examples/computer-vision/screens/StyleTransferScreen.tsx @@ -39,7 +39,10 @@ export const StyleTransferScreen = ({ if (!model.isReady) { return ( - + ); } diff --git a/ios/RnExecutorch/Classification.mm b/ios/RnExecutorch/Classification.mm index 96f510c748..0688a1803f 100644 --- a/ios/RnExecutorch/Classification.mm +++ b/ios/RnExecutorch/Classification.mm @@ -1,15 +1,14 @@ #import "Classification.h" -#import "utils/Fetcher.h" +#import "ImageProcessor.h" #import "models/BaseModel.h" +#import "models/classification/ClassificationModel.h" +#import "opencv2/opencv.hpp" #import "utils/ETError.h" -#import "ImageProcessor.h" #import #import -#import "models/classification/ClassificationModel.h" -#import "opencv2/opencv.hpp" @implementation Classification { - ClassificationModel* model; + ClassificationModel *model; } RCT_EXPORT_MODULE() @@ -18,16 +17,19 @@ - (void)loadModule:(NSString *)modelSource resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { model = [[ClassificationModel alloc] init]; - [model loadModel: [NSURL URLWithString:modelSource] completion:^(BOOL success, NSNumber *errorCode){ - if(success){ - resolve(errorCode); - return; - } - - reject(@"init_module_error", [NSString - stringWithFormat:@"%ld", (long)[errorCode longValue]], nil); - return; - }]; + [model + loadModel:[NSURL URLWithString:modelSource] + completion:^(BOOL success, NSNumber *errorCode) { + if (success) { + resolve(errorCode); + return; + } + + reject(@"init_module_error", + [NSString stringWithFormat:@"%ld", (long)[errorCode longValue]], + nil); + return; + }]; } - (void)forward:(NSString *)input @@ -36,20 +38,19 @@ - (void)forward:(NSString *)input @try { cv::Mat image = [ImageProcessor readImage:input]; NSDictionary *result = [model runModel:image]; - + resolve(result); return; } @catch (NSException *exception) { NSLog(@"An exception occurred: %@, %@", exception.name, exception.reason); - reject(@"forward_error", [NSString stringWithFormat:@"%@", exception.reason], - nil); + reject(@"forward_error", + [NSString stringWithFormat:@"%@", exception.reason], nil); return; } } - - (std::shared_ptr)getTurboModule: -(const facebook::react::ObjCTurboModule::InitParams &)params { + (const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared(params); } diff --git a/ios/RnExecutorch/ETModule.mm b/ios/RnExecutorch/ETModule.mm index e04dec88d1..abd202796b 100644 --- a/ios/RnExecutorch/ETModule.mm +++ b/ios/RnExecutorch/ETModule.mm @@ -1,5 +1,4 @@ #import "ETModule.h" -#import "utils/Fetcher.h" #import #import #include @@ -16,30 +15,25 @@ - (void)loadModule:(NSString *)modelSource if (!module) { module = [[ETModel alloc] init]; } - - [Fetcher fetchResource:[NSURL URLWithString:modelSource] - resourceType:ResourceType::MODEL - completionHandler:^(NSString *filePath, NSError *error) { - if (error) { - reject(@"init_module_error", @"-1", nil); - return; - } - - NSNumber *result = [self->module loadModel:filePath]; - if ([result isEqualToNumber:@(0)]) { - resolve(result); - } else { - NSError *error = [NSError - errorWithDomain:@"ETModuleErrorDomain" - code:[result intValue] + + NSURL *modelURL = [NSURL URLWithString:modelSource]; + + NSNumber *result = [self->module loadModel:modelURL.path]; + + if ([result intValue] != 0) { + NSError *error = + [NSError errorWithDomain:@"ETModuleErrorDomain" + code:[result intValue] userInfo:@{ - NSLocalizedDescriptionKey : [NSString - stringWithFormat:@"%ld", (long)[result longValue]] - }]; - - reject(@"init_module_error", error.localizedDescription, error); - } - }]; + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"%ld", (long)[result longValue]] + }]; + + reject(@"init_module_error", error.localizedDescription, error); + return; + } + + resolve(result); } - (void)forward:(NSArray *)input @@ -48,7 +42,9 @@ - (void)forward:(NSArray *)input resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { @try { - NSArray *result = [module forward:input shape:shape inputType:[NSNumber numberWithInt:inputType]]; + NSArray *result = [module forward:input + shape:shape + inputType:[NSNumber numberWithInt:inputType]]; resolve(result); } @catch (NSException *exception) { NSLog(@"An exception occurred: %@, %@", exception.name, exception.reason); @@ -69,7 +65,7 @@ - (void)loadMethod:(NSString *)methodName } - (std::shared_ptr)getTurboModule: -(const facebook::react::ObjCTurboModule::InitParams &)params { + (const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared(params); } diff --git a/ios/RnExecutorch/LLM.mm b/ios/RnExecutorch/LLM.mm index 8b6957ff4f..462c114279 100644 --- a/ios/RnExecutorch/LLM.mm +++ b/ios/RnExecutorch/LLM.mm @@ -1,34 +1,29 @@ #import "LLM.h" -#import -#import "utils/llms/ConversationManager.h" #import "utils/llms/Constants.h" -#import "utils/Fetcher.h" -#import "utils/LargeFileFetcher.h" -#import -#import +#import "utils/llms/ConversationManager.h" +#import #import #import #import #import #import #import +#import #import - +#import @implementation LLM { LLaMARunner *runner; ConversationManager *conversationManager; NSMutableString *tempLlamaResponse; - BOOL isFetching; } - (instancetype)init { self = [super init]; - if(self) { - isFetching = NO; + if (self) { tempLlamaResponse = [[NSMutableString alloc] init]; } - + return self; } @@ -38,99 +33,84 @@ - (void)onResult:(NSString *)token prompt:(NSString *)prompt { if ([token isEqualToString:prompt]) { return; } - + dispatch_async(dispatch_get_main_queue(), ^{ [self emitOnToken:token]; [self->tempLlamaResponse appendString:token]; }); } -- (void)updateDownloadProgress:(NSNumber *)progress { - dispatch_async(dispatch_get_main_queue(), ^{ - [self emitOnDownloadProgress:progress]; - }); -} - -- (void)loadLLM:(NSString *)modelSource tokenizerSource:(NSString *)tokenizerSource systemPrompt:(NSString *)systemPrompt contextWindowLength:(double)contextWindowLength resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { +- (void)loadLLM:(NSString *)modelSource + tokenizerSource:(NSString *)tokenizerSource + systemPrompt:(NSString *)systemPrompt + contextWindowLength:(double)contextWindowLength + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { NSURL *modelURL = [NSURL URLWithString:modelSource]; NSURL *tokenizerURL = [NSURL URLWithString:tokenizerSource]; - - if(isFetching){ - reject(@"model_is_fetching", @"Model is fetching", nil); + @try { + self->runner = [[LLaMARunner alloc] initWithModelPath:modelURL.path + tokenizerPath:tokenizerURL.path]; + NSUInteger contextWindowLengthUInt = (NSUInteger)round(contextWindowLength); + + self->conversationManager = [[ConversationManager alloc] + initWithNumMessagesContextWindow:contextWindowLengthUInt + systemPrompt:systemPrompt]; + + self->tempLlamaResponse = [NSMutableString string]; + resolve(@"Model and tokenizer loaded successfully"); + return; + } @catch (NSException *exception) { + reject(@"Model or tokenizer loading failed", exception.reason, nil); return; } - - isFetching = YES; - [Fetcher fetchResource:tokenizerURL resourceType:ResourceType::TOKENIZER completionHandler:^(NSString *tokenizerFilePath, NSError *error) { - if(error){ - reject(@"download_error", error.localizedDescription, nil); - return; - } - LargeFileFetcher *modelFetcher = [[LargeFileFetcher alloc] init]; - modelFetcher.onProgress = ^(NSNumber *progress) { - [self updateDownloadProgress:progress]; - }; - - modelFetcher.onFailure = ^(NSError *error){ - reject(@"download_error", error.localizedDescription, nil); - return; - }; - - modelFetcher.onFinish = ^(NSString *modelFilePath) { - self->runner = [[LLaMARunner alloc] initWithModelPath:modelFilePath tokenizerPath:tokenizerFilePath]; - NSUInteger contextWindowLengthUInt = (NSUInteger)round(contextWindowLength); - - self->conversationManager = [[ConversationManager alloc] initWithNumMessagesContextWindow: contextWindowLengthUInt systemPrompt: systemPrompt]; - self->isFetching = NO; - self->tempLlamaResponse = [NSMutableString string]; - resolve(@"Model and tokenizer loaded successfully"); - return; - }; - - [modelFetcher startDownloadingFileFromURL:modelURL]; - }]; } - -- (void) runInference:(NSString *)input resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { +- (void)runInference:(NSString *)input + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { [conversationManager addResponse:input senderRole:ChatRole::USER]; NSString *prompt = [conversationManager getConversation]; - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSError *error = nil; - [self->runner generate:prompt withTokenCallback:^(NSString *token) { - [self onResult:token prompt:prompt]; - } error:&error]; - - // make sure to add eot token once generation is done - if (![self->tempLlamaResponse hasSuffix:END_OF_TEXT_TOKEN_NS]) { - [self onResult:END_OF_TEXT_TOKEN_NS prompt:prompt]; - } - - if (self->tempLlamaResponse) { - [self->conversationManager addResponse:self->tempLlamaResponse senderRole:ChatRole::ASSISTANT]; - self->tempLlamaResponse = [NSMutableString string]; - } - - if (error) { - reject(@"error_in_generation", error.localizedDescription, nil); - return; - } - resolve(@"Inference completed successfully"); - return; - }); + + dispatch_async( + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSError *error = nil; + [self->runner generate:prompt + withTokenCallback:^(NSString *token) { + [self onResult:token prompt:prompt]; + } + error:&error]; + + // make sure to add eot token once generation is done + if (![self->tempLlamaResponse hasSuffix:END_OF_TEXT_TOKEN_NS]) { + [self onResult:END_OF_TEXT_TOKEN_NS prompt:prompt]; + } + + if (self->tempLlamaResponse) { + [self->conversationManager addResponse:self->tempLlamaResponse + senderRole:ChatRole::ASSISTANT]; + self->tempLlamaResponse = [NSMutableString string]; + } + + if (error) { + reject(@"error_in_generation", error.localizedDescription, nil); + return; + } + resolve(@"Inference completed successfully"); + return; + }); } --(void)interrupt { +- (void)interrupt { [self->runner stop]; } --(void)deleteModule { +- (void)deleteModule { self->runner = nil; } -- (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params -{ +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared(params); } diff --git a/ios/RnExecutorch/ObjectDetection.mm b/ios/RnExecutorch/ObjectDetection.mm index 785d07b7fa..2324db8ac0 100644 --- a/ios/RnExecutorch/ObjectDetection.mm +++ b/ios/RnExecutorch/ObjectDetection.mm @@ -1,8 +1,8 @@ #import "ObjectDetection.h" #import "models/object_detection/SSDLiteLargeModel.hpp" +#import "utils/ImageProcessor.h" #import #import -#import "utils/ImageProcessor.h" @implementation ObjectDetection { SSDLiteLargeModel *model; @@ -42,8 +42,8 @@ - (void)forward:(NSString *)input NSArray *result = [model runModel:image]; resolve(result); } @catch (NSException *exception) { - reject(@"forward_error", [NSString stringWithFormat:@"%@", exception.reason], - nil); + reject(@"forward_error", + [NSString stringWithFormat:@"%@", exception.reason], nil); } } diff --git a/ios/RnExecutorch/StyleTransfer.mm b/ios/RnExecutorch/StyleTransfer.mm index 6b3bba8c3a..08e8d4a34a 100644 --- a/ios/RnExecutorch/StyleTransfer.mm +++ b/ios/RnExecutorch/StyleTransfer.mm @@ -1,15 +1,14 @@ #import "StyleTransfer.h" -#import "utils/Fetcher.h" +#import "ImageProcessor.h" #import "models/BaseModel.h" +#import "models/StyleTransferModel.h" #import "utils/ETError.h" -#import "ImageProcessor.h" #import #import -#import "models/StyleTransferModel.h" #import @implementation StyleTransfer { - StyleTransferModel* model; + StyleTransferModel *model; } RCT_EXPORT_MODULE() @@ -18,16 +17,19 @@ - (void)loadModule:(NSString *)modelSource resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { model = [[StyleTransferModel alloc] init]; - [model loadModel: [NSURL URLWithString:modelSource] completion:^(BOOL success, NSNumber *errorCode){ - if(success){ - resolve(errorCode); - return; - } - - reject(@"init_module_error", [NSString - stringWithFormat:@"%ld", (long)[errorCode longValue]], nil); - return; - }]; + [model + loadModel:[NSURL URLWithString:modelSource] + completion:^(BOOL success, NSNumber *errorCode) { + if (success) { + resolve(errorCode); + return; + } + + reject(@"init_module_error", + [NSString stringWithFormat:@"%ld", (long)[errorCode longValue]], + nil); + return; + }]; } - (void)forward:(NSString *)input @@ -36,21 +38,20 @@ - (void)forward:(NSString *)input @try { cv::Mat image = [ImageProcessor readImage:input]; cv::Mat resultImage = [model runModel:image]; - - NSString* tempFilePath = [ImageProcessor saveToTempFile:resultImage]; + + NSString *tempFilePath = [ImageProcessor saveToTempFile:resultImage]; resolve(tempFilePath); return; } @catch (NSException *exception) { NSLog(@"An exception occurred: %@, %@", exception.name, exception.reason); - reject(@"forward_error", [NSString stringWithFormat:@"%@", exception.reason], - nil); + reject(@"forward_error", + [NSString stringWithFormat:@"%@", exception.reason], nil); return; } } - - (std::shared_ptr)getTurboModule: -(const facebook::react::ObjCTurboModule::InitParams &)params { + (const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared(params); } diff --git a/ios/RnExecutorch/models/BaseModel.h b/ios/RnExecutorch/models/BaseModel.h index d5259e6b1f..147215b687 100644 --- a/ios/RnExecutorch/models/BaseModel.h +++ b/ios/RnExecutorch/models/BaseModel.h @@ -1,14 +1,14 @@ +#import "ExecutorchLib/ETModel.h" #import #import -#import "ExecutorchLib/ETModel.h" -@interface BaseModel : NSObject -{ +@interface BaseModel : NSObject { @protected ETModel *module; } - (NSArray *)forward:(NSArray *)input; -- (void)loadModel:(NSURL *)modelURL completion:(void (^)(BOOL success, NSNumber *code))completion; +- (void)loadModel:(NSURL *)modelURL + completion:(void (^)(BOOL success, NSNumber *code))completion; @end diff --git a/ios/RnExecutorch/models/BaseModel.mm b/ios/RnExecutorch/models/BaseModel.mm index b06a3530f6..587eddf68b 100644 --- a/ios/RnExecutorch/models/BaseModel.mm +++ b/ios/RnExecutorch/models/BaseModel.mm @@ -1,30 +1,27 @@ #import "BaseModel.h" #import "../utils/ETError.h" -#import "../utils/Fetcher.h" @implementation BaseModel - (NSArray *)forward:(NSArray *)input { - NSArray *result = [module forward:input shape:[module getInputShape:@0] inputType:[module getInputType: @0]]; + NSArray *result = [module forward:input + shape:[module getInputShape:@0] + inputType:[module getInputType:@0]]; return result; } -- (void)loadModel:(NSURL *)modelURL completion:(void (^)(BOOL success, NSNumber* code))completion { +- (void)loadModel:(NSURL *)modelURL + completion:(void (^)(BOOL success, NSNumber *code))completion { module = [[ETModel alloc] init]; - [Fetcher fetchResource:modelURL resourceType:ResourceType::MODEL completionHandler:^(NSString *filePath, NSError *error) { - if (error) { - completion(NO, @(InvalidModelSource)); - return; - } - NSNumber *result = [self->module loadModel: filePath]; - if([result intValue] != 0){ - completion(NO, result); - return; - } - - completion(YES, result); + + NSNumber *result = [self->module loadModel:modelURL.path]; + if ([result intValue] != 0) { + completion(NO, result); return; - }]; + } + + completion(YES, result); + return; } @end diff --git a/ios/RnExecutorch/utils/Fetcher.h b/ios/RnExecutorch/utils/Fetcher.h deleted file mode 100644 index 9d75a5740b..0000000000 --- a/ios/RnExecutorch/utils/Fetcher.h +++ /dev/null @@ -1,34 +0,0 @@ -#import -#import - -enum class ResourceType -{ - MODEL, - TOKENIZER -}; - -inline constexpr unsigned int STATUS_OK = 200; - -inline constexpr unsigned int INVALID_URL_CODE = 1; -inline constexpr unsigned int INVALID_FILE_TYPE = 2; -inline constexpr unsigned int DOWNLOAD_FAILED = 3; -inline constexpr unsigned int WRITE_FAILED = 4; -inline constexpr unsigned int NO_DATA = 5; - -NSString *const FETCHER_ERROR_DOMAIN = @"com.swmansion.fetcher"; - -@interface Fetcher : NSObject - -@property(nonatomic, copy) void (^onProgress)(NSNumber *); - -+ (void)fetchResource:(NSURL *)resourceURL resourceType:(ResourceType)resourceType completionHandler:(void (^)(NSString *filePath, NSError *error))completionHandler; -+ (BOOL)isValidURL:(NSURL *)url; -+ (BOOL)isLocalFilePath:(NSURL *)url; -+ (BOOL)hasValidExtension:(NSString *)fileName resourceType:(ResourceType)resourceType; -+ (BOOL)fileExistsAtPath:(NSString *)filePath; -+ (NSString *)extractFilePathFromAssetsURL:(NSURL *)resourceURL; -+ (NSError *)buildError:(NSString *)description code:(NSInteger)code; -+ (NSURL *)createDirectoryInDocuments:(NSFileManager *)fileManager; -+ (NSString *)prepareFilePathForResource:(NSURL *)resourceURL resourceType:(ResourceType)resourceType error:(NSError **)error; - -@end diff --git a/ios/RnExecutorch/utils/Fetcher.mm b/ios/RnExecutorch/utils/Fetcher.mm deleted file mode 100644 index 27ae238e70..0000000000 --- a/ios/RnExecutorch/utils/Fetcher.mm +++ /dev/null @@ -1,167 +0,0 @@ -#import -#import "Fetcher.h" - -@implementation Fetcher - -+ (NSError *) buildError:(NSString *)description code: (NSInteger)code { - NSDictionary *userInfo = @{NSLocalizedDescriptionKey: NSLocalizedString(description, nil)}; - NSError *error = [NSError errorWithDomain:FETCHER_ERROR_DOMAIN code:code userInfo:userInfo]; - return error; -} - -+ (NSString *) extractFilePathFromAssetsURL: (NSURL *)resourceURL { - NSString *fileName = nil; - - NSURLComponents *components = [NSURLComponents componentsWithURL:resourceURL resolvingAgainstBaseURL:NO]; - NSArray *queryItems = components.queryItems; - - for (NSURLQueryItem *item in queryItems) { - if ([item.name isEqualToString:@"unstable_path"]) { - NSString *unstablePathValue = item.value; - - if (unstablePathValue) { - NSString *decodedPath = [unstablePathValue stringByRemovingPercentEncoding]; - NSArray *pathComponents = [decodedPath componentsSeparatedByString:@"?"]; - - fileName = [[pathComponents firstObject] lastPathComponent]; - } - break; - } - } - - return fileName ? fileName : @""; -} - -+ (BOOL) isValidURL:(NSURL *) url { - return url && url.scheme && url.host; -} - -+ (BOOL) isLocalFilePath:(NSURL *) url { - return [url.scheme isEqualToString:@"file"]; -} - -+ (BOOL) hasValidExtension:(NSString *)fileName resourceType:(ResourceType)resourceType{ - switch (resourceType) { - case ResourceType::TOKENIZER: - return [fileName hasSuffix:@".bin"]; - case ResourceType::MODEL: - return [fileName hasSuffix:@".pte"]; - default: - return NO; - } -} - -+(BOOL) fileExistsAtPath:(NSString *)filePath { - NSFileManager *fileManager = [NSFileManager defaultManager]; - return [fileManager fileExistsAtPath:filePath]; -} - -+ (NSURL *) createDirectoryInDocuments:(NSFileManager *)fileManager{ - NSArray *documentsDirectoryURLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]; - NSURL *documentsDirectoryURL = [documentsDirectoryURLs firstObject]; - NSURL *modelsDirectoryURL = [documentsDirectoryURL URLByAppendingPathComponent:@"models" isDirectory:YES]; - - NSError *directoryError = nil; - if (![fileManager fileExistsAtPath:modelsDirectoryURL.path]) { - [fileManager createDirectoryAtURL:modelsDirectoryURL withIntermediateDirectories:YES attributes:nil error:&directoryError]; - if (directoryError) { - return [NSURL URLWithString: @""]; - } - } - - return modelsDirectoryURL; -} - -+ (NSString *)prepareFilePathForResource:(NSURL *)resourceURL resourceType:(ResourceType)resourceType error:(NSError **)error { - NSFileManager *fileManager = [NSFileManager defaultManager]; - - //If fileName is equal to assets it means that the model is included in bundle - NSString *fileName = [resourceURL lastPathComponent]; - - // If fileName is "assets", extract the file path - if ([fileName isEqualToString:@"assets"]) { - fileName = [self extractFilePathFromAssetsURL:resourceURL]; - } - - //Check if the file extension matches type of resource - if (![Fetcher hasValidExtension:fileName resourceType:resourceType]) { - if (error) { - *error = [self buildError:@"Invalid file type" code:INVALID_FILE_TYPE]; - } - return nil; - } - - //Create models directory in app's documents folder - NSURL *modelsDirectoryURL = [self createDirectoryInDocuments:fileManager]; - if ([@"" isEqualToString: modelsDirectoryURL.path]) { - if (error) { - *error = [self buildError:@"Couldn't create models directory" code:WRITE_FAILED]; - } - return nil; - } - - // Construct the full file path - NSString *filePath = [modelsDirectoryURL.path stringByAppendingPathComponent:fileName]; - - //Check if file with extracted file name already exists in models directory if so, return path to it - if ([fileManager fileExistsAtPath:filePath]) { - return filePath; - } - - return filePath; -} - -+ (void)fetchResource:(NSURL *)resourceURL resourceType:(ResourceType)resourceType completionHandler:(void (^)(NSString *filePath, NSError *error))completionHandler { - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSError *error = nil; - if (![self isValidURL:resourceURL]) { - if ([self isLocalFilePath:resourceURL] && [self fileExistsAtPath:resourceURL.path]) { - completionHandler(resourceURL.path, nil); - return; - } - completionHandler(nil, [self buildError:@"The provided URL is invalid" code:INVALID_URL_CODE]); - return; - } - - NSString *filePath = [self prepareFilePathForResource:resourceURL resourceType:resourceType error:&error]; - if (error) { - completionHandler(nil, error); - return; - } - - if ([fileManager fileExistsAtPath:filePath]) { - completionHandler(filePath, nil); - return; - } - - NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration]; - - NSURLSessionDataTask *downloadTask = [session dataTaskWithURL:resourceURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *sessionTaskError) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - NSInteger statusCode = httpResponse.statusCode; - - if (statusCode != STATUS_OK) { - completionHandler(nil, [self buildError:@"Couldn't download file" code:DOWNLOAD_FAILED]); - return; - } - if (sessionTaskError) { - completionHandler(nil, sessionTaskError); - return; - } - if (data) { - NSError *writeError = nil; - BOOL success = [data writeToFile:filePath options:NSDataWritingAtomic error:&writeError]; - if (success) { - completionHandler(filePath, nil); - } else { - completionHandler(nil, [self buildError:writeError.localizedDescription code:WRITE_FAILED]); - } - } else { - completionHandler(nil, [self buildError:@"No data received from server" code:NO_DATA]); - } - }]; - - [downloadTask resume]; -} -@end diff --git a/ios/RnExecutorch/utils/LargeFileFetcher.h b/ios/RnExecutorch/utils/LargeFileFetcher.h deleted file mode 100644 index ae2cf100c8..0000000000 --- a/ios/RnExecutorch/utils/LargeFileFetcher.h +++ /dev/null @@ -1,13 +0,0 @@ -// Downloader.h - -#import - -@interface LargeFileFetcher : NSObject - -@property(nonatomic, copy) void (^onProgress)(NSNumber *); -@property(nonatomic, copy) void (^onFinish)(NSString *); -@property(nonatomic, copy) void (^onFailure)(NSError *); - -- (void)startDownloadingFileFromURL:(NSURL *)url; - -@end diff --git a/ios/RnExecutorch/utils/LargeFileFetcher.mm b/ios/RnExecutorch/utils/LargeFileFetcher.mm deleted file mode 100644 index 48cc39b94a..0000000000 --- a/ios/RnExecutorch/utils/LargeFileFetcher.mm +++ /dev/null @@ -1,127 +0,0 @@ -#import "LargeFileFetcher.h" -#import "Fetcher.h" -#import - -@implementation LargeFileFetcher { - NSURLSession *_session; - NSURL *_fileURL; - NSString *_fileName; - NSString *_destination; -} - -- (instancetype)init { - self = [super init]; - if (self) { - NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[NSString stringWithFormat:@"com.swmansion.rnexecutorch.%@", [[NSUUID UUID] UUIDString]]]; - _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; - } - return self; -} - -- (void)executeCompletionWithSuccess:(NSString *)filePath { - if (self.onFinish) { - dispatch_async(dispatch_get_main_queue(), ^{ - self.onFinish(filePath); - }); - } -} - -- (void)executeFailureWithMessage:(NSString *)message code:(NSInteger)code { - if (self.onFailure) { - NSError *error = [Fetcher buildError:message code:code]; - dispatch_async(dispatch_get_main_queue(), ^{ - self.onFailure(error); - }); - } -} - -- (void)cancelUnfinishedTasks { - [_session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { - - for (NSURLSessionDownloadTask *downloadTask in downloadTasks) { - [downloadTask cancel]; - } - }]; -} - -- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { - double progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite; - - if(self.onProgress){ - dispatch_async(dispatch_get_main_queue(), ^{ - self.onProgress(@(progress)); - }); - } -} - -- (void)sendHeadRequestToURL:(NSURL *)url { - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; - [request setHTTPMethod:@"HEAD"]; - NSURLSessionDataTask *dataTask = [_session dataTaskWithRequest:request]; - [dataTask resume]; -} - -- (void)startDownloadingFileFromURL:(NSURL *)url { - //Check if file is a valid url, if not check if it's path to local file - if (![Fetcher isValidURL:url]) { - if([Fetcher isLocalFilePath:url] && [Fetcher fileExistsAtPath:url.path]){ - [self executeCompletionWithSuccess:url.path]; - return; - } - [self executeFailureWithMessage:@"The provided URL is invalid" code:INVALID_URL_CODE]; - return; - } - - //Check if file with extracted file name already exists in models directory if so, return path to it - NSError *error; - NSString *filePath = [Fetcher prepareFilePathForResource:url resourceType:ResourceType::MODEL error:&error]; - if (error) { - [self executeFailureWithMessage:error.localizedDescription code:error.code]; - return; - } - - if([[NSFileManager defaultManager] fileExistsAtPath: filePath]){ - [self executeCompletionWithSuccess:filePath]; - return; - } - - // If the url is a Software Mansion HuggingFace repo, we want to send a HEAD - // request to the config.json file, this increments HF download counter - // https://huggingface.co/docs/hub/models-download-stats - NSString *huggingFaceOrgNSString = @"https://huggingface.co/software-mansion/"; - NSString *modelURLNSString = [url absoluteString]; - - if ([modelURLNSString hasPrefix:huggingFaceOrgNSString]) { - NSRange resolveRange = [modelURLNSString rangeOfString:@"resolve"]; - if (resolveRange.location != NSNotFound) { - NSString *configURLNSString = [modelURLNSString substringToIndex:resolveRange.location + resolveRange.length]; - configURLNSString = [configURLNSString stringByAppendingString:@"/main/config.json"]; - NSURL *configNSURL = [NSURL URLWithString:configURLNSString]; - [self sendHeadRequestToURL:configNSURL]; - } - } - - //Cancel all running background download tasks and start new one - _destination = filePath; - [self cancelUnfinishedTasks]; - NSURLRequest *request = [NSURLRequest requestWithURL:url]; - NSURLSessionDownloadTask *downloadTask = [_session downloadTaskWithRequest:request]; - [downloadTask resume]; -} - -- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { - NSFileManager *fileManager = [NSFileManager defaultManager]; - - [fileManager removeItemAtPath:_destination error:nil]; - - NSError *error; - if ([fileManager moveItemAtPath:location.path toPath:_destination error:&error]) { - [self executeCompletionWithSuccess:_destination]; - return; - } else { - [self executeFailureWithMessage:@"Failed to write file to disk" code:WRITE_FAILED]; - return; - } -} - -@end diff --git a/package.json b/package.json index 9947d4cb95..23f5a0b349 100644 --- a/package.json +++ b/package.json @@ -191,5 +191,9 @@ "android": { "javaPackageName": "com.swmansion.rnexecutorch" } + }, + "dependencies": { + "expo-asset": "^11.0.3", + "expo-file-system": "^18.0.10" } } diff --git a/src/constants/directories.ts b/src/constants/directories.ts new file mode 100644 index 0000000000..ac20d04d8f --- /dev/null +++ b/src/constants/directories.ts @@ -0,0 +1,3 @@ +import { documentDirectory } from 'expo-file-system'; + +export const RNEDirectory = `${documentDirectory}react-native-executorch/`; diff --git a/src/hooks/computer_vision/useClassification.ts b/src/hooks/computer_vision/useClassification.ts index 836a0fc938..0cc8a1dccd 100644 --- a/src/hooks/computer_vision/useClassification.ts +++ b/src/hooks/computer_vision/useClassification.ts @@ -1,6 +1,6 @@ import { useState } from 'react'; import { _ClassificationModule } from '../../native/RnExecutorchModules'; -import { useModule } from '../../useModule'; +import { useModule } from '../useModule'; interface Props { modelSource: string | number; @@ -12,6 +12,7 @@ export const useClassification = ({ error: string | null; isReady: boolean; isGenerating: boolean; + downloadProgress: number; forward: (input: string) => Promise<{ [category: string]: number }>; } => { const [module, _] = useState(() => new _ClassificationModule()); @@ -19,11 +20,12 @@ export const useClassification = ({ error, isReady, isGenerating, + downloadProgress, forwardImage: forward, } = useModule({ modelSource, module, }); - return { error, isReady, isGenerating, forward }; + return { error, isReady, isGenerating, downloadProgress, forward }; }; diff --git a/src/hooks/computer_vision/useObjectDetection.ts b/src/hooks/computer_vision/useObjectDetection.ts index 8456ee338a..b5082540a2 100644 --- a/src/hooks/computer_vision/useObjectDetection.ts +++ b/src/hooks/computer_vision/useObjectDetection.ts @@ -1,6 +1,6 @@ import { useState } from 'react'; import { _ObjectDetectionModule } from '../../native/RnExecutorchModules'; -import { useModule } from '../../useModule'; +import { useModule } from '../useModule'; import { Detection } from '../../types/object_detection'; interface Props { @@ -13,6 +13,7 @@ export const useObjectDetection = ({ error: string | null; isReady: boolean; isGenerating: boolean; + downloadProgress: number; forward: (input: string) => Promise; } => { const [module, _] = useState(() => new _ObjectDetectionModule()); @@ -20,11 +21,12 @@ export const useObjectDetection = ({ error, isReady, isGenerating, + downloadProgress, forwardImage: forward, } = useModule({ modelSource, module, }); - return { error, isReady, isGenerating, forward }; + return { error, isReady, isGenerating, downloadProgress, forward }; }; diff --git a/src/hooks/computer_vision/useStyleTransfer.ts b/src/hooks/computer_vision/useStyleTransfer.ts index 20c400b41e..b7dcbaca0d 100644 --- a/src/hooks/computer_vision/useStyleTransfer.ts +++ b/src/hooks/computer_vision/useStyleTransfer.ts @@ -1,6 +1,6 @@ import { useState } from 'react'; import { _StyleTransferModule } from '../../native/RnExecutorchModules'; -import { useModule } from '../../useModule'; +import { useModule } from '../useModule'; interface Props { modelSource: string | number; @@ -12,6 +12,7 @@ export const useStyleTransfer = ({ error: string | null; isReady: boolean; isGenerating: boolean; + downloadProgress: number; forward: (input: string) => Promise; } => { const [module, _] = useState(() => new _StyleTransferModule()); @@ -19,11 +20,12 @@ export const useStyleTransfer = ({ error, isReady, isGenerating, + downloadProgress, forwardImage: forward, } = useModule({ modelSource, module, }); - return { error, isReady, isGenerating, forward }; + return { error, isReady, isGenerating, downloadProgress, forward }; }; diff --git a/src/hooks/general/useExecutorchModule.ts b/src/hooks/general/useExecutorchModule.ts index 5a180fdff0..99e2a2fcf8 100644 --- a/src/hooks/general/useExecutorchModule.ts +++ b/src/hooks/general/useExecutorchModule.ts @@ -1,6 +1,6 @@ import { useState } from 'react'; import { _ETModule } from '../../native/RnExecutorchModules'; -import { useModule } from '../../useModule'; +import { useModule } from '../useModule'; import { ETInput } from '../../types/common'; import { getError } from '../../Error'; @@ -14,6 +14,7 @@ export const useExecutorchModule = ({ error: string | null; isReady: boolean; isGenerating: boolean; + downloadProgress: number; forward: (input: ETInput, shape: number[]) => Promise; loadMethod: (methodName: string) => Promise; loadForward: () => Promise; @@ -23,6 +24,7 @@ export const useExecutorchModule = ({ error, isReady, isGenerating, + downloadProgress, forwardETInput: forward, } = useModule({ modelSource, @@ -45,6 +47,7 @@ export const useExecutorchModule = ({ error, isReady, isGenerating, + downloadProgress, forward, loadMethod, loadForward, diff --git a/src/hooks/natural_language_processing/useLLM.ts b/src/hooks/natural_language_processing/useLLM.ts index 386750c459..5864d40bc4 100644 --- a/src/hooks/natural_language_processing/useLLM.ts +++ b/src/hooks/natural_language_processing/useLLM.ts @@ -1,12 +1,13 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import { EventSubscription, Image } from 'react-native'; +import { EventSubscription } from 'react-native'; +import { LLM } from '../../native/RnExecutorchModules'; +import { fetchResource } from '../../utils/fetchResource'; import { ResourceSource, Model } from '../../types/common'; import { DEFAULT_CONTEXT_WINDOW_LENGTH, DEFAULT_SYSTEM_PROMPT, EOT_TOKEN, } from '../../constants/llamaDefaults'; -import { LLM } from '../../native/RnExecutorchModules'; const interrupt = () => { LLM.interrupt(); @@ -28,33 +29,22 @@ export const useLLM = ({ const [isGenerating, setIsGenerating] = useState(false); const [response, setResponse] = useState(''); const [downloadProgress, setDownloadProgress] = useState(0); - const downloadProgressListener = useRef(null); const tokenGeneratedListener = useRef(null); useEffect(() => { const loadModel = async () => { try { - let modelUrl = modelSource; - let tokenizerUrl = tokenizerSource; - if (typeof modelSource === 'number') { - modelUrl = Image.resolveAssetSource(modelSource).uri; - } - if (typeof tokenizerSource === 'number') { - tokenizerUrl = Image.resolveAssetSource(tokenizerSource).uri; - } + setIsReady(false); - downloadProgressListener.current = LLM.onDownloadProgress( - (data: number) => { - if (data) { - setDownloadProgress(data); - } - } + const tokenizerFileUri = await fetchResource(tokenizerSource); + const modelFileUri = await fetchResource( + modelSource, + setDownloadProgress ); - setIsReady(false); await LLM.loadLLM( - modelUrl as string, - tokenizerUrl as string, + modelFileUri, + tokenizerFileUri, systemPrompt, contextWindowLength ); @@ -85,8 +75,6 @@ export const useLLM = ({ loadModel(); return () => { - downloadProgressListener.current?.remove(); - downloadProgressListener.current = null; tokenGeneratedListener.current?.remove(); tokenGeneratedListener.current = null; LLM.deleteModule(); diff --git a/src/useModule.ts b/src/hooks/useModule.ts similarity index 79% rename from src/useModule.ts rename to src/hooks/useModule.ts index e4080a55f9..bba9638b70 100644 --- a/src/useModule.ts +++ b/src/hooks/useModule.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; -import { Image } from 'react-native'; -import { ETError, getError } from './Error'; -import { ETInput, Module, getTypeIdentifier } from './types/common'; +import { fetchResource } from '../utils/fetchResource'; +import { ETError, getError } from '../Error'; +import { ETInput, Module, getTypeIdentifier } from '../types/common'; interface Props { modelSource: string | number; @@ -12,6 +12,7 @@ interface _Module { error: string | null; isReady: boolean; isGenerating: boolean; + downloadProgress: number; forwardETInput: (input: ETInput, shape: number[]) => Promise; forwardImage: (input: string) => Promise; } @@ -20,19 +21,16 @@ export const useModule = ({ modelSource, module }: Props): _Module => { const [error, setError] = useState(null); const [isReady, setIsReady] = useState(false); const [isGenerating, setIsGenerating] = useState(false); + const [downloadProgress, setDownloadProgress] = useState(0); useEffect(() => { const loadModel = async () => { if (!modelSource) return; - let path = modelSource; - - if (typeof modelSource === 'number') { - path = Image.resolveAssetSource(modelSource).uri; - } try { setIsReady(false); - await module.loadModule(path); + const fileUri = await fetchResource(modelSource, setDownloadProgress); + await module.loadModule(fileUri); setIsReady(true); } catch (e) { setError(getError(e)); @@ -86,5 +84,12 @@ export const useModule = ({ modelSource, module }: Props): _Module => { } }; - return { error, isReady, isGenerating, forwardETInput, forwardImage }; + return { + error, + isReady, + isGenerating, + downloadProgress, + forwardETInput, + forwardImage, + }; }; diff --git a/src/index.tsx b/src/index.tsx index 1c21b25e08..d07c8302cd 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -16,6 +16,9 @@ export * from './modules/natural_language_processing/LLMModule'; export * from './modules/general/ExecutorchModule'; +// utils +export * from './utils/listResources'; + // types export * from './types/object_detection'; diff --git a/src/modules/BaseModule.ts b/src/modules/BaseModule.ts index a93591cee1..e977836f77 100644 --- a/src/modules/BaseModule.ts +++ b/src/modules/BaseModule.ts @@ -1,10 +1,10 @@ -import { Image } from 'react-native'; import { _StyleTransferModule, _ObjectDetectionModule, _ClassificationModule, _ETModule, } from '../native/RnExecutorchModules'; +import { fetchResource } from '../utils/fetchResource'; import { ResourceSource } from '../types/common'; import { getError } from '../Error'; @@ -15,16 +15,17 @@ export class BaseModule { | _ClassificationModule | _ETModule; + static onDownloadProgressCallback = (_downloadProgress: number) => {}; + static async load(modelSource: ResourceSource) { if (!modelSource) return; - let path = - typeof modelSource === 'number' - ? Image.resolveAssetSource(modelSource).uri - : modelSource; - try { - await this.module.loadModule(path); + const fileUri = await fetchResource( + modelSource, + this.onDownloadProgressCallback + ); + await this.module.loadModule(fileUri); } catch (e) { throw new Error(getError(e)); } @@ -33,4 +34,8 @@ export class BaseModule { static async forward(..._: any[]): Promise { throw new Error('The forward method is not implemented.'); } + + static onDownloadProgress(callback: (downloadProgress: number) => void) { + this.onDownloadProgressCallback = callback; + } } diff --git a/src/modules/natural_language_processing/LLMModule.ts b/src/modules/natural_language_processing/LLMModule.ts index 20914eddd9..7b44e8a90f 100644 --- a/src/modules/natural_language_processing/LLMModule.ts +++ b/src/modules/natural_language_processing/LLMModule.ts @@ -1,5 +1,5 @@ import { LLM } from '../../native/RnExecutorchModules'; -import { Image } from 'react-native'; +import { fetchResource } from '../../utils/fetchResource'; import { DEFAULT_CONTEXT_WINDOW_LENGTH, DEFAULT_SYSTEM_PROMPT, @@ -7,6 +7,8 @@ import { import { ResourceSource } from '../../types/common'; export class LLMModule { + static onDownloadProgressCallback = (_downloadProgress: number) => {}; + static async load( modelSource: ResourceSource, tokenizerSource: ResourceSource, @@ -14,19 +16,15 @@ export class LLMModule { contextWindowLength = DEFAULT_CONTEXT_WINDOW_LENGTH ) { try { - let modelUrl = - typeof modelSource === 'number' - ? Image.resolveAssetSource(modelSource).uri - : modelSource; - - let tokenizerUrl = - typeof tokenizerSource === 'number' - ? Image.resolveAssetSource(tokenizerSource).uri - : tokenizerSource; + const tokenizerFileUri = await fetchResource(tokenizerSource); + const modelFileUri = await fetchResource( + modelSource, + this.onDownloadProgressCallback + ); await LLM.loadLLM( - modelUrl, - tokenizerUrl, + modelFileUri, + tokenizerFileUri, systemPrompt, contextWindowLength ); @@ -43,8 +41,8 @@ export class LLMModule { } } - static onDownloadProgress(callback: (data: number) => void) { - return LLM.onDownloadProgress(callback); + static onDownloadProgress(callback: (downloadProgress: number) => void) { + this.onDownloadProgressCallback = callback; } static onToken(callback: (data: string | undefined) => void) { diff --git a/src/native/NativeLLM.ts b/src/native/NativeLLM.ts index 35d2a42087..b610e30e39 100644 --- a/src/native/NativeLLM.ts +++ b/src/native/NativeLLM.ts @@ -14,7 +14,6 @@ export interface Spec extends TurboModule { deleteModule(): void; readonly onToken: EventEmitter; - readonly onDownloadProgress: EventEmitter; } export default TurboModuleRegistry.get('LLM'); diff --git a/src/utils/FetchResource.ts b/src/utils/FetchResource.ts new file mode 100644 index 0000000000..9885758ee5 --- /dev/null +++ b/src/utils/FetchResource.ts @@ -0,0 +1,82 @@ +import { + cacheDirectory, + createDownloadResumable, + getInfoAsync, + makeDirectoryAsync, + moveAsync, + FileSystemSessionType, +} from 'expo-file-system'; +import { Asset } from 'expo-asset'; +import { RNEDirectory } from '../constants/directories'; + +const getFilenameFromUri = (uri: string) => { + const filename = uri.split('/').pop()?.split('?')[0]; + if (!filename) { + throw new Error('Cannot derive filename from URI'); + } + return filename; +}; + +/** + * Increments the Hugging Face download counter if the URI points to a Software Mansion Hugging Face repo. + * More information: https://huggingface.co/docs/hub/models-download-stats + */ +const triggerHuggingFaceDownloadCounter = (uri: string) => { + const url = new URL(uri); + if ( + url.host === 'huggingface.co' && + url.pathname.startsWith('/software-mansion/') + ) { + const baseUrl = `${url.protocol}//${url.host}${url.pathname.split('resolve')[0]}`; + fetch(`${baseUrl}resolve/main/config.json`, { method: 'HEAD' }); + } +}; + +export const fetchResource = async ( + source: string | number, + callback: (downloadProgress: number) => void = () => {} +) => { + const uri = + typeof source === 'number' ? Asset.fromModule(source).uri : source; + const filename = getFilenameFromUri(uri); + const fileUri = `${RNEDirectory}${filename}`; + + // Check if the file already exists + if ((await getInfoAsync(fileUri)).exists) { + return fileUri; + } + + // Create the RNEDirectory if it doesn't exist + if (!(await getInfoAsync(RNEDirectory)).exists) { + await makeDirectoryAsync(RNEDirectory, { intermediates: true }); + } + + // Handle local asset files in release mode + if (!uri.includes('://')) { + const asset = Asset.fromModule(source); + const fileUriWithType = `${fileUri}.${asset.type}`; + await asset.downloadAsync(); + await moveAsync({ from: asset.localUri!, to: fileUriWithType }); + return fileUriWithType; + } + + // Handle remote file download + const cacheFileUri = `${cacheDirectory}${filename}`; + const downloadResumable = createDownloadResumable( + uri, + cacheFileUri, + { sessionType: FileSystemSessionType.BACKGROUND }, + ({ totalBytesWritten, totalBytesExpectedToWrite }) => { + callback(totalBytesWritten / totalBytesExpectedToWrite); + } + ); + const result = await downloadResumable.downloadAsync(); + if (!result || result.status !== 200) { + throw new Error(`Failed to fetch resource from '${uri}'`); + } + await moveAsync({ from: cacheFileUri, to: fileUri }); + + triggerHuggingFaceDownloadCounter(uri); + + return fileUri; +}; diff --git a/src/utils/listResources.ts b/src/utils/listResources.ts new file mode 100644 index 0000000000..84531ac14e --- /dev/null +++ b/src/utils/listResources.ts @@ -0,0 +1,12 @@ +import { readDirectoryAsync } from 'expo-file-system'; +import { RNEDirectory } from '../constants/directories'; + +export const listFiles = async () => { + const files = await readDirectoryAsync(RNEDirectory); + return files.map((file) => `${RNEDirectory}${file}`); +}; + +export const listModels = async () => { + const files = await listFiles(); + return files.filter((file) => file.endsWith('.pte')); +}; diff --git a/yarn.lock b/yarn.lock index fa968b1bdf..77c1e49cbb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26,6 +26,15 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:~7.10.4": + version: 7.10.4 + resolution: "@babel/code-frame@npm:7.10.4" + dependencies: + "@babel/highlight": ^7.10.4 + checksum: feb4543c8a509fe30f0f6e8d7aa84f82b41148b963b826cd330e34986f649a85cb63b2f13dd4effdf434ac555d16f14940b8ea5f4433297c2f5ff85486ded019 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.9, @babel/compat-data@npm:^7.26.0": version: 7.26.2 resolution: "@babel/compat-data@npm:7.26.2" @@ -306,6 +315,18 @@ __metadata: languageName: node linkType: hard +"@babel/highlight@npm:^7.10.4": + version: 7.25.9 + resolution: "@babel/highlight@npm:7.25.9" + dependencies: + "@babel/helper-validator-identifier": ^7.25.9 + chalk: ^2.4.2 + js-tokens: ^4.0.0 + picocolors: ^1.0.0 + checksum: a6e0ac0a1c4bef7401915ca3442ab2b7ae4adf360262ca96b91396bfb9578abb28c316abf5e34460b780696db833b550238d9256bdaca60fade4ba7a67645064 + languageName: node + linkType: hard + "@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.25.9, @babel/parser@npm:^7.26.0, @babel/parser@npm:^7.26.2": version: 7.26.2 resolution: "@babel/parser@npm:7.26.2" @@ -1904,6 +1925,125 @@ __metadata: languageName: node linkType: hard +"@expo/config-plugins@npm:~9.0.15": + version: 9.0.15 + resolution: "@expo/config-plugins@npm:9.0.15" + dependencies: + "@expo/config-types": ^52.0.4 + "@expo/json-file": ~9.0.1 + "@expo/plist": ^0.2.1 + "@expo/sdk-runtime-versions": ^1.0.0 + chalk: ^4.1.2 + debug: ^4.3.5 + getenv: ^1.0.0 + glob: ^10.4.2 + resolve-from: ^5.0.0 + semver: ^7.5.4 + slash: ^3.0.0 + slugify: ^1.6.6 + xcode: ^3.0.1 + xml2js: 0.6.0 + checksum: ac45abd0b23048eb1e38eb1dc8c543c713c0066cc5c05a0685267ce4d3f769731ac39dd249fa88b5263a8c02aea920ab3e79c4ad2ac23e05f5b1d77ba1e47ef7 + languageName: node + linkType: hard + +"@expo/config-types@npm:^52.0.4": + version: 52.0.4 + resolution: "@expo/config-types@npm:52.0.4" + checksum: d9489d0bf5c619d68938673b6fbfa5f4ebe626316e95763f0f396c9e09bff1adc2d00cc814088e2e0039c0b344b57df580439e38bef16ed43d2d5d40c87d453f + languageName: node + linkType: hard + +"@expo/config@npm:~10.0.9": + version: 10.0.10 + resolution: "@expo/config@npm:10.0.10" + dependencies: + "@babel/code-frame": ~7.10.4 + "@expo/config-plugins": ~9.0.15 + "@expo/config-types": ^52.0.4 + "@expo/json-file": ^9.0.2 + deepmerge: ^4.3.1 + getenv: ^1.0.0 + glob: ^10.4.2 + require-from-string: ^2.0.2 + resolve-from: ^5.0.0 + resolve-workspace-root: ^2.0.0 + semver: ^7.6.0 + slugify: ^1.3.4 + sucrase: 3.35.0 + checksum: ba9c4a4eaa714824ecc88d27df09bef532268abcad25fd06cc79dfbb8f592e591d3d3afd3288041535be94155536eb5f0c65ab718f661f2d16cbcb881b003a71 + languageName: node + linkType: hard + +"@expo/env@npm:~0.4.1": + version: 0.4.2 + resolution: "@expo/env@npm:0.4.2" + dependencies: + chalk: ^4.0.0 + debug: ^4.3.4 + dotenv: ~16.4.5 + dotenv-expand: ~11.0.6 + getenv: ^1.0.0 + checksum: cc9264e50faf5f38e6253b5c97e775bc8cb29bf8ca37bcd427cbb67dd773a4e62a2bdb030904565bac4644eac89e10fc61206d5aa42e5b1f26acf5ca1f6b9ce9 + languageName: node + linkType: hard + +"@expo/image-utils@npm:^0.6.4": + version: 0.6.5 + resolution: "@expo/image-utils@npm:0.6.5" + dependencies: + "@expo/spawn-async": ^1.7.2 + chalk: ^4.0.0 + fs-extra: 9.0.0 + getenv: ^1.0.0 + jimp-compact: 0.16.1 + parse-png: ^2.1.0 + resolve-from: ^5.0.0 + semver: ^7.6.0 + temp-dir: ~2.0.0 + unique-string: ~2.0.0 + checksum: f6fe5efd518d84463d767a4fb8a920d8b70779c8d93ba07ef407e0f016452324e3da6cff8292d0e2b436facdaef0073b8d527881e73ff5ba0288b4c942cdb539 + languageName: node + linkType: hard + +"@expo/json-file@npm:^9.0.2, @expo/json-file@npm:~9.0.1": + version: 9.0.2 + resolution: "@expo/json-file@npm:9.0.2" + dependencies: + "@babel/code-frame": ~7.10.4 + json5: ^2.2.3 + write-file-atomic: ^2.3.0 + checksum: 665fb72028e403adcb3ff9d7763ff6fab0ce16eaa1485a6b502daaab709608a9953599cce2f5c46e91b4791bd2380c87eb911deef4161b9d1f3a7631c2630366 + languageName: node + linkType: hard + +"@expo/plist@npm:^0.2.1": + version: 0.2.2 + resolution: "@expo/plist@npm:0.2.2" + dependencies: + "@xmldom/xmldom": ~0.7.7 + base64-js: ^1.2.3 + xmlbuilder: ^14.0.0 + checksum: ccc8256f07352e327092132d885c3e2291f14b3ef6060065eb11080f130a575012cfff7ae92c579b5e04cc6b2587930caed70e277c2f1f5b63591e39366e659a + languageName: node + linkType: hard + +"@expo/sdk-runtime-versions@npm:^1.0.0": + version: 1.0.0 + resolution: "@expo/sdk-runtime-versions@npm:1.0.0" + checksum: 0942d5a356f590e8dc795761456cc48b3e2d6a38ad2a02d6774efcdc5a70424e05623b4e3e5d2fec0cdc30f40dde05c14391c781607eed3971bf8676518bfd9d + languageName: node + linkType: hard + +"@expo/spawn-async@npm:^1.7.2": + version: 1.7.2 + resolution: "@expo/spawn-async@npm:1.7.2" + dependencies: + cross-spawn: ^7.0.3 + checksum: d99e5ff6d303ec9b0105f97c4fa6c65bca526c7d4d0987997c35cc745fa8224adf009942d01808192ebb9fa30619a53316641958631e85cf17b773d9eeda2597 + languageName: node + linkType: hard + "@hapi/hoek@npm:^9.0.0, @hapi/hoek@npm:^9.3.0": version: 9.3.0 resolution: "@hapi/hoek@npm:9.3.0" @@ -2245,6 +2385,17 @@ __metadata: languageName: node linkType: hard +"@jridgewell/gen-mapping@npm:^0.3.2": + version: 0.3.8 + resolution: "@jridgewell/gen-mapping@npm:0.3.8" + dependencies: + "@jridgewell/set-array": ^1.2.1 + "@jridgewell/sourcemap-codec": ^1.4.10 + "@jridgewell/trace-mapping": ^0.3.24 + checksum: c0687b5227461717aa537fe71a42e356bcd1c43293b3353796a148bf3b0d6f59109def46c22f05b60e29a46f19b2e4676d027959a7c53a6c92b9d5b0d87d0420 + languageName: node + linkType: hard + "@jridgewell/gen-mapping@npm:^0.3.5": version: 0.3.5 resolution: "@jridgewell/gen-mapping@npm:0.3.5" @@ -3195,6 +3346,20 @@ __metadata: languageName: node linkType: hard +"@xmldom/xmldom@npm:^0.8.8": + version: 0.8.10 + resolution: "@xmldom/xmldom@npm:0.8.10" + checksum: 4c136aec31fb3b49aaa53b6fcbfe524d02a1dc0d8e17ee35bd3bf35e9ce1344560481cd1efd086ad1a4821541482528672306d5e37cdbd187f33d7fadd3e2cf0 + languageName: node + linkType: hard + +"@xmldom/xmldom@npm:~0.7.7": + version: 0.7.13 + resolution: "@xmldom/xmldom@npm:0.7.13" + checksum: b4054078530e5fa8ede9677425deff0fce6d965f4c477ca73f8490d8a089e60b8498a15560425a1335f5ff99ecb851ed2c734b0a9a879299a5694302f212f37a + languageName: node + linkType: hard + "JSONStream@npm:^1.0.4, JSONStream@npm:^1.3.5": version: 1.3.5 resolution: "JSONStream@npm:1.3.5" @@ -3368,7 +3533,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^3.2.0": +"ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" dependencies: @@ -3400,6 +3565,13 @@ __metadata: languageName: node linkType: hard +"any-promise@npm:^1.0.0": + version: 1.3.0 + resolution: "any-promise@npm:1.3.0" + checksum: 0ee8a9bdbe882c90464d75d1f55cf027f5458650c4bd1f0467e65aec38ccccda07ca5844969ee77ed46d04e7dded3eaceb027e8d32f385688523fe305fa7e1de + languageName: node + linkType: hard + "anymatch@npm:^3.0.3": version: 3.1.3 resolution: "anymatch@npm:3.1.3" @@ -3582,6 +3754,13 @@ __metadata: languageName: node linkType: hard +"at-least-node@npm:^1.0.0": + version: 1.0.0 + resolution: "at-least-node@npm:1.0.0" + checksum: 463e2f8e43384f1afb54bc68485c436d7622acec08b6fad269b421cb1d29cebb5af751426793d0961ed243146fe4dc983402f6d5a51b720b277818dbf6f2e49e + languageName: node + linkType: hard + "available-typed-arrays@npm:^1.0.7": version: 1.0.7 resolution: "available-typed-arrays@npm:1.0.7" @@ -3762,13 +3941,20 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": +"base64-js@npm:^1.2.3, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 languageName: node linkType: hard +"big-integer@npm:1.6.x": + version: 1.6.52 + resolution: "big-integer@npm:1.6.52" + checksum: 6e86885787a20fed96521958ae9086960e4e4b5e74d04f3ef7513d4d0ad631a9f3bde2730fc8aaa4b00419fc865f6ec573e5320234531ef37505da7da192c40b + languageName: node + linkType: hard + "bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -3780,6 +3966,24 @@ __metadata: languageName: node linkType: hard +"bplist-creator@npm:0.1.1": + version: 0.1.1 + resolution: "bplist-creator@npm:0.1.1" + dependencies: + stream-buffers: 2.2.x + checksum: b0d40d1d1623f1afdbb575cfc8075d742d2c4f0eb458574be809e3857752d1042a39553b3943d2d7f505dde92bcd43e1d7bdac61c9cd44475d696deb79f897ce + languageName: node + linkType: hard + +"bplist-parser@npm:0.3.2": + version: 0.3.2 + resolution: "bplist-parser@npm:0.3.2" + dependencies: + big-integer: 1.6.x + checksum: fad0f6eb155a9b636b4096a1725ce972a0386490d7d38df7be11a3a5645372446b7c44aacbc6626d24d2c17d8b837765361520ebf2960aeffcaf56765811620e + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -3831,6 +4035,30 @@ __metadata: languageName: node linkType: hard +"buffer-alloc-unsafe@npm:^1.1.0": + version: 1.1.0 + resolution: "buffer-alloc-unsafe@npm:1.1.0" + checksum: c5e18bf51f67754ec843c9af3d4c005051aac5008a3992938dda1344e5cfec77c4b02b4ca303644d1e9a6e281765155ce6356d85c6f5ccc5cd21afc868def396 + languageName: node + linkType: hard + +"buffer-alloc@npm:^1.1.0": + version: 1.2.0 + resolution: "buffer-alloc@npm:1.2.0" + dependencies: + buffer-alloc-unsafe: ^1.1.0 + buffer-fill: ^1.0.0 + checksum: 560cd27f3cbe73c614867da373407d4506309c62fe18de45a1ce191f3785ec6ca2488d802ff82065798542422980ca25f903db078c57822218182c37c3576df5 + languageName: node + linkType: hard + +"buffer-fill@npm:^1.0.0": + version: 1.0.0 + resolution: "buffer-fill@npm:1.0.0" + checksum: c29b4723ddeab01e74b5d3b982a0c6828f2ded49cef049ddca3dac661c874ecdbcecb5dd8380cf0f4adbeb8cff90a7de724126750a1f1e5ebd4eb6c59a1315b1 + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -3964,6 +4192,17 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^2.4.2": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: ^3.2.1 + escape-string-regexp: ^1.0.5 + supports-color: ^5.3.0 + checksum: ec3661d38fe77f681200f878edbd9448821924e0f93a9cefc0e26a33b145f1027a2084bf19967160d11e1f03bfe4eaffcabf5493b89098b2782c3fe0b03d80c2 + languageName: node + linkType: hard + "chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -4194,6 +4433,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^4.0.0": + version: 4.1.1 + resolution: "commander@npm:4.1.1" + checksum: d7b9913ff92cae20cb577a4ac6fcc121bd6223319e54a40f51a14740a681ad5c574fd29a57da478a5f234a6fa6c52cbf0b7c641353e03c648b1ae85ba670b977 + languageName: node + linkType: hard + "commander@npm:^9.4.1": version: 9.5.0 resolution: "commander@npm:9.5.0" @@ -4629,6 +4875,13 @@ __metadata: languageName: node linkType: hard +"crypto-random-string@npm:^2.0.0": + version: 2.0.0 + resolution: "crypto-random-string@npm:2.0.0" + checksum: 0283879f55e7c16fdceacc181f87a0a65c53bc16ffe1d58b9d19a6277adcd71900d02bb2c4843dd55e78c51e30e89b0fec618a7f170ebcc95b33182c28f05fd6 + languageName: node + linkType: hard + "csstype@npm:^3.0.2": version: 3.1.3 resolution: "csstype@npm:3.1.3" @@ -4711,6 +4964,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.3.5": + version: 4.4.0 + resolution: "debug@npm:4.4.0" + dependencies: + ms: ^2.1.3 + peerDependenciesMeta: + supports-color: + optional: true + checksum: fb42df878dd0e22816fc56e1fdca9da73caa85212fbe40c868b1295a6878f9101ae684f4eeef516c13acfc700f5ea07f1136954f43d4cd2d477a811144136479 + languageName: node + linkType: hard + "decamelize-keys@npm:^1.1.0": version: 1.1.1 resolution: "decamelize-keys@npm:1.1.1" @@ -4761,7 +5026,7 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.0": +"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.0, deepmerge@npm:^4.3.1": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" checksum: 2024c6a980a1b7128084170c4cf56b0fd58a63f2da1660dcfe977415f27b17dbe5888668b59d0b063753f3220719d5e400b7f113609489c90160bb9a5518d052 @@ -4922,6 +5187,22 @@ __metadata: languageName: node linkType: hard +"dotenv-expand@npm:~11.0.6": + version: 11.0.7 + resolution: "dotenv-expand@npm:11.0.7" + dependencies: + dotenv: ^16.4.5 + checksum: 58455ad9ffedbf6180b49f8f35596da54f10b02efcaabcba5400363f432e1da057113eee39b42365535da41df1e794d54a4aa67b22b37c41686c3dce4e6a28c5 + languageName: node + linkType: hard + +"dotenv@npm:^16.4.5, dotenv@npm:~16.4.5": + version: 16.4.7 + resolution: "dotenv@npm:16.4.7" + checksum: c27419b5875a44addcc56cc69b7dc5b0e6587826ca85d5b355da9303c6fc317fc9989f1f18366a16378c9fdd9532d14117a1abe6029cc719cdbbef6eaef2cea4 + languageName: node + linkType: hard + "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -5586,6 +5867,47 @@ __metadata: languageName: node linkType: hard +"expo-asset@npm:^11.0.3": + version: 11.0.3 + resolution: "expo-asset@npm:11.0.3" + dependencies: + "@expo/image-utils": ^0.6.4 + expo-constants: ~17.0.5 + invariant: ^2.2.4 + md5-file: ^3.2.3 + peerDependencies: + expo: "*" + react: "*" + react-native: "*" + checksum: e22714d0277dd7c3f8ece9ebc6e6475fb4fd96aa54c4ff42878837aef62f2d334232cd7a5afded0b5c55f18a7d4fa2737bce7920666525112c5ceee1d7e5c4fd + languageName: node + linkType: hard + +"expo-constants@npm:~17.0.5": + version: 17.0.6 + resolution: "expo-constants@npm:17.0.6" + dependencies: + "@expo/config": ~10.0.9 + "@expo/env": ~0.4.1 + peerDependencies: + expo: "*" + react-native: "*" + checksum: c4719a9c2eaa2c3aaa447efee0a3a50320a68f1106b5cf581139ad0d64585edfaad082218f9cb506239958a7fb96d261e95dcbeb8fd23aa5941fc2e169314a00 + languageName: node + linkType: hard + +"expo-file-system@npm:^18.0.10": + version: 18.0.10 + resolution: "expo-file-system@npm:18.0.10" + dependencies: + web-streams-polyfill: ^3.3.2 + peerDependencies: + expo: "*" + react-native: "*" + checksum: 86884a5046267c4a58ee8f283fe2e6f4b8dec6666f31c3db2f7460d4ebc792f18d42ef7d33c9f5da047d626343538fec707598983f15e7452775767ff3e72035 + languageName: node + linkType: hard + "exponential-backoff@npm:^3.1.1": version: 3.1.1 resolution: "exponential-backoff@npm:3.1.1" @@ -5819,6 +6141,18 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:9.0.0": + version: 9.0.0 + resolution: "fs-extra@npm:9.0.0" + dependencies: + at-least-node: ^1.0.0 + graceful-fs: ^4.2.0 + jsonfile: ^6.0.1 + universalify: ^1.0.0 + checksum: c4269fbfd8d8d2a1edca4257fa28545caf7e5ad218d264f723c338a154d3624d2ef098c19915b9436d3186b7ac45d5b032371a2004008ec0cd4072512e853aa8 + languageName: node + linkType: hard + "fs-extra@npm:^10.1.0": version: 10.1.0 resolution: "fs-extra@npm:10.1.0" @@ -5997,6 +6331,13 @@ __metadata: languageName: node linkType: hard +"getenv@npm:^1.0.0": + version: 1.0.0 + resolution: "getenv@npm:1.0.0" + checksum: 19ae5cad603a1cf1bcb8fa3bed48e00d062eb0572a4404c02334b67f3b3499f238383082b064bb42515e9e25c2b08aef1a3e3d2b6852347721aa8b174825bd56 + languageName: node + linkType: hard + "git-raw-commits@npm:^2.0.11, git-raw-commits@npm:^2.0.8": version: 2.0.11 resolution: "git-raw-commits@npm:2.0.11" @@ -6061,7 +6402,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2, glob@npm:^10.3.10": +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.2": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -6233,6 +6574,13 @@ __metadata: languageName: node linkType: hard +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 4a15638b454bf086c8148979aae044dd6e39d63904cd452d970374fa6a87623423da485dfb814e7be882e05c096a7ccf1ebd48e7e7501d0208d8384ff4dea73b + languageName: node + linkType: hard + "has-flag@npm:^4.0.0": version: 4.0.0 resolution: "has-flag@npm:4.0.0" @@ -7530,6 +7878,13 @@ __metadata: languageName: node linkType: hard +"jimp-compact@npm:0.16.1": + version: 0.16.1 + resolution: "jimp-compact@npm:0.16.1" + checksum: 5a1c62d70881b31f79ea65fecfe03617be0eb56139bc451f37e8972365c99ac3b52c5176c446ff27144c98ab664a99107ae08d347044e94e1de637f165b41a57 + languageName: node + linkType: hard + "joi@npm:^17.2.1": version: 17.13.3 resolution: "joi@npm:17.13.3" @@ -8083,6 +8438,17 @@ __metadata: languageName: node linkType: hard +"md5-file@npm:^3.2.3": + version: 3.2.3 + resolution: "md5-file@npm:3.2.3" + dependencies: + buffer-alloc: ^1.1.0 + bin: + md5-file: cli.js + checksum: a3738274ee0c5ce21e7c14a4b60e5de6b298740f8a37eeb502bb97a056e3f19ea0871418b4dd45ca9c70d2f1d6c79a19e9a320fba1c129b196cdf671e544c450 + languageName: node + linkType: hard + "memoize-one@npm:^5.0.0": version: 5.2.1 resolution: "memoize-one@npm:5.2.1" @@ -8908,6 +9274,17 @@ __metadata: languageName: node linkType: hard +"mz@npm:^2.7.0": + version: 2.7.0 + resolution: "mz@npm:2.7.0" + dependencies: + any-promise: ^1.0.0 + object-assign: ^4.0.1 + thenify-all: ^1.0.0 + checksum: 8427de0ece99a07e9faed3c0c6778820d7543e3776f9a84d22cf0ec0a8eb65f6e9aee9c9d353ff9a105ff62d33a9463c6ca638974cc652ee8140cd1e35951c87 + languageName: node + linkType: hard + "natural-compare-lite@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare-lite@npm:1.4.0" @@ -9104,7 +9481,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4.1.1": +"object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f @@ -9397,6 +9774,15 @@ __metadata: languageName: node linkType: hard +"parse-png@npm:^2.1.0": + version: 2.1.0 + resolution: "parse-png@npm:2.1.0" + dependencies: + pngjs: ^3.3.0 + checksum: 0c6b6c42c8830cd16f6f9e9aedafd53111c0ad2ff350ba79c629996887567558f5639ad0c95764f96f7acd1f9ff63d4ac73737e80efa3911a6de9839ee520c96 + languageName: node + linkType: hard + "parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" @@ -9500,7 +9886,7 @@ __metadata: languageName: node linkType: hard -"pirates@npm:^4.0.4, pirates@npm:^4.0.6": +"pirates@npm:^4.0.1, pirates@npm:^4.0.4, pirates@npm:^4.0.6": version: 4.0.6 resolution: "pirates@npm:4.0.6" checksum: 46a65fefaf19c6f57460388a5af9ab81e3d7fd0e7bc44ca59d753cb5c4d0df97c6c6e583674869762101836d68675f027d60f841c105d72734df9dfca97cbcc6 @@ -9534,6 +9920,24 @@ __metadata: languageName: node linkType: hard +"plist@npm:^3.0.5": + version: 3.1.0 + resolution: "plist@npm:3.1.0" + dependencies: + "@xmldom/xmldom": ^0.8.8 + base64-js: ^1.5.1 + xmlbuilder: ^15.1.1 + checksum: c8ea013da8646d4c50dff82f9be39488054621cc229957621bb00add42b5d4ce3657cf58d4b10c50f7dea1a81118f825838f838baeb4e6f17fab453ecf91d424 + languageName: node + linkType: hard + +"pngjs@npm:^3.3.0": + version: 3.4.0 + resolution: "pngjs@npm:3.4.0" + checksum: 8bd40bd698abd16b72c97b85cb858c80894fbedc76277ce72a784aa441e14795d45d9856e97333ca469b34b67528860ffc8a7317ca6beea349b645366df00bcd + languageName: node + linkType: hard + "possible-typed-array-names@npm:^1.0.0": version: 1.0.0 resolution: "possible-typed-array-names@npm:1.0.0" @@ -9790,6 +10194,8 @@ __metadata: eslint: ^8.51.0 eslint-config-prettier: ^9.0.0 eslint-plugin-prettier: ^5.0.1 + expo-asset: ^11.0.3 + expo-file-system: ^18.0.10 jest: ^29.7.0 metro-react-native-babel-preset: ^0.77.0 prettier: ^3.0.3 @@ -10178,6 +10584,13 @@ __metadata: languageName: node linkType: hard +"resolve-workspace-root@npm:^2.0.0": + version: 2.0.0 + resolution: "resolve-workspace-root@npm:2.0.0" + checksum: c7222391a35ecb3514fa04d753334a86f984d8ffe06ce87506582c4c5671ac608273b8f5e6faa2055be6e196785bf751ede9a48d484de53889d721b814c097ab + languageName: node + linkType: hard + "resolve.exports@npm:^2.0.0": version: 2.0.2 resolution: "resolve.exports@npm:2.0.2" @@ -10336,6 +10749,13 @@ __metadata: languageName: node linkType: hard +"sax@npm:>=0.6.0": + version: 1.4.1 + resolution: "sax@npm:1.4.1" + checksum: 3ad64df16b743f0f2eb7c38ced9692a6d924f1cd07bbe45c39576c2cf50de8290d9d04e7b2228f924c7d05fecc4ec5cf651423278e0c7b63d260c387ef3af84a + languageName: node + linkType: hard + "scheduler@npm:0.24.0-canary-efb381bbf-20230505": version: 0.24.0-canary-efb381bbf-20230505 resolution: "scheduler@npm:0.24.0-canary-efb381bbf-20230505" @@ -10404,6 +10824,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.6.0": + version: 7.7.1 + resolution: "semver@npm:7.7.1" + bin: + semver: bin/semver.js + checksum: 586b825d36874007c9382d9e1ad8f93888d8670040add24a28e06a910aeebd673a2eb9e3bf169c6679d9245e66efb9057e0852e70d9daa6c27372aab1dda7104 + languageName: node + linkType: hard + "send@npm:0.19.0": version: 0.19.0 resolution: "send@npm:0.19.0" @@ -10542,6 +10971,17 @@ __metadata: languageName: node linkType: hard +"simple-plist@npm:^1.1.0": + version: 1.4.0 + resolution: "simple-plist@npm:1.4.0" + dependencies: + bplist-creator: 0.1.1 + bplist-parser: 0.3.2 + plist: ^3.0.5 + checksum: fa8086f6b781c289f1abad21306481dda4af6373b32a5d998a70e53c2b7218a1d21ebb5ae3e736baae704c21d311d3d39d01d0e6a2387eda01b4020b9ebd909e + languageName: node + linkType: hard + "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -10574,6 +11014,13 @@ __metadata: languageName: node linkType: hard +"slugify@npm:^1.3.4, slugify@npm:^1.6.6": + version: 1.6.6 + resolution: "slugify@npm:1.6.6" + checksum: 04773c2d3b7aea8d2a61fa47cc7e5d29ce04e1a96cbaec409da57139df906acb3a449fac30b167d203212c806e73690abd4ff94fbad0a9a7b7ea109a2a638ae9 + languageName: node + linkType: hard + "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -10750,6 +11197,13 @@ __metadata: languageName: node linkType: hard +"stream-buffers@npm:2.2.x": + version: 2.2.0 + resolution: "stream-buffers@npm:2.2.0" + checksum: 4587d9e8f050d689fb38b4295e73408401b16de8edecc12026c6f4ae92956705ecfd995ae3845d7fa3ebf19502d5754df9143d91447fd881d86e518f43882c1c + languageName: node + linkType: hard + "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -10951,6 +11405,24 @@ __metadata: languageName: node linkType: hard +"sucrase@npm:3.35.0": + version: 3.35.0 + resolution: "sucrase@npm:3.35.0" + dependencies: + "@jridgewell/gen-mapping": ^0.3.2 + commander: ^4.0.0 + glob: ^10.3.10 + lines-and-columns: ^1.1.6 + mz: ^2.7.0 + pirates: ^4.0.1 + ts-interface-checker: ^0.1.9 + bin: + sucrase: bin/sucrase + sucrase-node: bin/sucrase-node + checksum: 9fc5792a9ab8a14dcf9c47dcb704431d35c1cdff1d17d55d382a31c2e8e3063870ad32ce120a80915498486246d612e30cda44f1624d9d9a10423e1a43487ad1 + languageName: node + linkType: hard + "sudo-prompt@npm:^9.0.0": version: 9.2.1 resolution: "sudo-prompt@npm:9.2.1" @@ -10958,6 +11430,15 @@ __metadata: languageName: node linkType: hard +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: ^3.0.0 + checksum: 95f6f4ba5afdf92f495b5a912d4abee8dcba766ae719b975c56c084f5004845f6f5a5f7769f52d53f40e21952a6d87411bafe34af4a01e65f9926002e38e1dac + languageName: node + linkType: hard + "supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -11007,6 +11488,13 @@ __metadata: languageName: node linkType: hard +"temp-dir@npm:~2.0.0": + version: 2.0.0 + resolution: "temp-dir@npm:2.0.0" + checksum: cc4f0404bf8d6ae1a166e0e64f3f409b423f4d1274d8c02814a59a5529f07db6cd070a749664141b992b2c1af337fa9bb451a460a43bb9bcddc49f235d3115aa + languageName: node + linkType: hard + "temp@npm:^0.8.4": version: 0.8.4 resolution: "temp@npm:0.8.4" @@ -11055,6 +11543,24 @@ __metadata: languageName: node linkType: hard +"thenify-all@npm:^1.0.0": + version: 1.6.0 + resolution: "thenify-all@npm:1.6.0" + dependencies: + thenify: ">= 3.1.0 < 4" + checksum: dba7cc8a23a154cdcb6acb7f51d61511c37a6b077ec5ab5da6e8b874272015937788402fd271fdfc5f187f8cb0948e38d0a42dcc89d554d731652ab458f5343e + languageName: node + linkType: hard + +"thenify@npm:>= 3.1.0 < 4": + version: 3.3.1 + resolution: "thenify@npm:3.3.1" + dependencies: + any-promise: ^1.0.0 + checksum: 84e1b804bfec49f3531215f17b4a6e50fd4397b5f7c1bccc427b9c656e1ecfb13ea79d899930184f78bc2f57285c54d9a50a590c8868f4f0cef5c1d9f898b05e + languageName: node + linkType: hard + "throat@npm:^5.0.0": version: 5.0.0 resolution: "throat@npm:5.0.0" @@ -11132,6 +11638,13 @@ __metadata: languageName: node linkType: hard +"ts-interface-checker@npm:^0.1.9": + version: 0.1.13 + resolution: "ts-interface-checker@npm:0.1.13" + checksum: 20c29189c2dd6067a8775e07823ddf8d59a33e2ffc47a1bd59a5cb28bb0121a2969a816d5e77eda2ed85b18171aa5d1c4005a6b88ae8499ec7cc49f78571cb5e + languageName: node + linkType: hard + "ts-node@npm:^10.8.1": version: 10.9.2 resolution: "ts-node@npm:10.9.2" @@ -11494,6 +12007,15 @@ __metadata: languageName: node linkType: hard +"unique-string@npm:~2.0.0": + version: 2.0.0 + resolution: "unique-string@npm:2.0.0" + dependencies: + crypto-random-string: ^2.0.0 + checksum: ef68f639136bcfe040cf7e3cd7a8dff076a665288122855148a6f7134092e6ed33bf83a7f3a9185e46c98dddc445a0da6ac25612afa1a7c38b8b654d6c02498e + languageName: node + linkType: hard + "universalify@npm:^0.1.0": version: 0.1.2 resolution: "universalify@npm:0.1.2" @@ -11501,6 +12023,13 @@ __metadata: languageName: node linkType: hard +"universalify@npm:^1.0.0": + version: 1.0.0 + resolution: "universalify@npm:1.0.0" + checksum: 095a808f2b915e3b89d29b6f3b4ee4163962b02fa5b7cb686970b8d0439f4ca789bc43f319b7cbb1ce552ae724e631d148e5aee9ce04c4f46a7fe0c5bbfd2b9e + languageName: node + linkType: hard + "universalify@npm:^2.0.0": version: 2.0.1 resolution: "universalify@npm:2.0.1" @@ -11552,6 +12081,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^7.0.3": + version: 7.0.3 + resolution: "uuid@npm:7.0.3" + bin: + uuid: dist/bin/uuid + checksum: f5b7b5cc28accac68d5c083fd51cca64896639ebd4cca88c6cfb363801aaa83aa439c86dfc8446ea250a7a98d17afd2ad9e88d9d4958c79a412eccb93bae29de + languageName: node + linkType: hard + "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" @@ -11612,6 +12150,13 @@ __metadata: languageName: node linkType: hard +"web-streams-polyfill@npm:^3.3.2": + version: 3.3.3 + resolution: "web-streams-polyfill@npm:3.3.3" + checksum: 21ab5ea08a730a2ef8023736afe16713b4f2023ec1c7085c16c8e293ee17ed085dff63a0ad8722da30c99c4ccbd4ccd1b2e79c861829f7ef2963d7de7004c2cb + languageName: node + linkType: hard + "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -11822,6 +12367,47 @@ __metadata: languageName: node linkType: hard +"xcode@npm:^3.0.1": + version: 3.0.1 + resolution: "xcode@npm:3.0.1" + dependencies: + simple-plist: ^1.1.0 + uuid: ^7.0.3 + checksum: 908ff85851f81aec6e36ca24427db092e1cc068f052716e14de5e762196858039efabbe053a1abe8920184622501049e74a93618e8692b982f7604a9847db108 + languageName: node + linkType: hard + +"xml2js@npm:0.6.0": + version: 0.6.0 + resolution: "xml2js@npm:0.6.0" + dependencies: + sax: ">=0.6.0" + xmlbuilder: ~11.0.0 + checksum: 437f353fd66d367bf158e9555a0625df9965d944e499728a5c6bc92a54a2763179b144f14b7e1c725040f56bbd22b0fa6cfcb09ec4faf39c45ce01efe631f40b + languageName: node + linkType: hard + +"xmlbuilder@npm:^14.0.0": + version: 14.0.0 + resolution: "xmlbuilder@npm:14.0.0" + checksum: 9e93d3c73957dbb21acde63afa5d241b19057bdbdca9d53534d8351e70f1d5c9db154e3ca19bd3e9ea84c082539ab6e7845591c8778a663e8b5d3470d5427a8b + languageName: node + linkType: hard + +"xmlbuilder@npm:^15.1.1": + version: 15.1.1 + resolution: "xmlbuilder@npm:15.1.1" + checksum: 14f7302402e28d1f32823583d121594a9dca36408d40320b33f598bd589ca5163a352d076489c9c64d2dc1da19a790926a07bf4191275330d4de2b0d85bb1843 + languageName: node + linkType: hard + +"xmlbuilder@npm:~11.0.0": + version: 11.0.1 + resolution: "xmlbuilder@npm:11.0.1" + checksum: 7152695e16f1a9976658215abab27e55d08b1b97bca901d58b048d2b6e106b5af31efccbdecf9b07af37c8377d8e7e821b494af10b3a68b0ff4ae60331b415b0 + languageName: node + linkType: hard + "xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2" From 65868fd3f65e5466685fab23b8abc2bdf265f357 Mon Sep 17 00:00:00 2001 From: Jakub Mroz <115979017+jakmro@users.noreply.github.com> Date: Tue, 18 Feb 2025 14:46:17 +0100 Subject: [PATCH 2/3] Rename FetchResource.ts to fetchResource.ts --- src/utils/{FetchResource.ts => fetchResource.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/utils/{FetchResource.ts => fetchResource.ts} (100%) diff --git a/src/utils/FetchResource.ts b/src/utils/fetchResource.ts similarity index 100% rename from src/utils/FetchResource.ts rename to src/utils/fetchResource.ts From 53d7fab0da861815616da372c94fa5a8adf8287a Mon Sep 17 00:00:00 2001 From: jakmro Date: Tue, 18 Feb 2025 16:10:40 +0100 Subject: [PATCH 3/3] Rename functions listFiles -> listDownloadedFiles, listModels -> listDownloadedModels --- src/index.tsx | 2 +- src/utils/{listResources.ts => listDownloadedResources.ts} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/utils/{listResources.ts => listDownloadedResources.ts} (66%) diff --git a/src/index.tsx b/src/index.tsx index d07c8302cd..ec7dd8c128 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -17,7 +17,7 @@ export * from './modules/natural_language_processing/LLMModule'; export * from './modules/general/ExecutorchModule'; // utils -export * from './utils/listResources'; +export * from './utils/listDownloadedResources'; // types export * from './types/object_detection'; diff --git a/src/utils/listResources.ts b/src/utils/listDownloadedResources.ts similarity index 66% rename from src/utils/listResources.ts rename to src/utils/listDownloadedResources.ts index 84531ac14e..44cb74f1f0 100644 --- a/src/utils/listResources.ts +++ b/src/utils/listDownloadedResources.ts @@ -1,12 +1,12 @@ import { readDirectoryAsync } from 'expo-file-system'; import { RNEDirectory } from '../constants/directories'; -export const listFiles = async () => { +export const listDownloadedFiles = async () => { const files = await readDirectoryAsync(RNEDirectory); return files.map((file) => `${RNEDirectory}${file}`); }; -export const listModels = async () => { - const files = await listFiles(); +export const listDownloadedModels = async () => { + const files = await listDownloadedFiles(); return files.filter((file) => file.endsWith('.pte')); };