react-native-executorch
Version:
An easy way to run AI models in React Native with ExecuTorch
256 lines (227 loc) • 6.63 kB
text/typescript
import { ResourceSource } from '..';
import { getModelNameForUrl } from '../constants/modelUrls';
import {
DOWNLOAD_EVENT_ENDPOINT,
LIB_VERSION,
} from '../constants/resourceFetcher';
/**
* Http status codes
* @category Types
*/
export enum HTTP_CODE {
/**
Everything is ok.
*/
OK = 200,
/**
Server has fulfilled a client request for a specific part of a resource, instead of sending the entire file.
*/
PARTIAL_CONTENT = 206,
}
/**
* Download status of the file.
* @category Types
*/
export enum DownloadStatus {
/**
* Download is still in progress.
*/
ONGOING,
/**
* Download is paused.
*/
PAUSED,
}
/**
* Types of sources that can be downloaded
* @category Types
*/
export enum SourceType {
/**
* Represents a raw object or data structure.
*/
OBJECT,
/**
* Represents a file stored locally on the filesystem.
*/
LOCAL_FILE,
/**
* Represents a file bundled with the application in release mode.
*/
RELEASE_MODE_FILE,
/**
* Represents a file served via the metro bundler during development.
*/
DEV_MODE_FILE,
/**
* Represents a file located at a remote URL.
*/
REMOTE_FILE,
}
/**
* Extended interface for resource sources, tracking download state and file locations.
* @category Interfaces
*/
export interface ResourceSourceExtended {
/**
* The original source definition.
*/
source: ResourceSource;
/**
* The type of the source (local, remote, etc.).
*/
sourceType: SourceType;
/**
* Optional callback to report download progress (0 to 1).
*/
callback?: (downloadProgress: number) => void;
/**
* Array of paths or identifiers for the resulting files.
*/
results: string[];
/**
* The URI of the resource.
*/
uri?: string;
/**
* The local file URI where the resource is stored.
*/
fileUri?: string;
/**
* The URI where the file is cached.
*/
cacheFileUri?: string;
/**
* Reference to the next resource in a linked chain of resources.
*/
next?: ResourceSourceExtended;
}
/**
* Utility functions for fetching and managing resources.
* @category Utilities - General
*/
export namespace ResourceFetcherUtils {
/**
* Removes the 'file://' prefix from a URI if it exists.
* @param uri - The URI to process.
* @returns The URI without the 'file://' prefix.
*/
export function removeFilePrefix(uri: string) {
return uri.startsWith('file://') ? uri.slice(7) : uri;
}
/**
* Generates a hash from a string representation of an object.
* @param jsonString - The stringified JSON object to hash.
* @returns The resulting hash as a string.
*/
export function hashObject(jsonString: string) {
let hash = 0;
for (let i = 0; i < jsonString.length; i++) {
// eslint-disable-next-line no-bitwise
hash = (hash << 5) - hash + jsonString.charCodeAt(i);
// eslint-disable-next-line no-bitwise
hash |= 0;
}
// eslint-disable-next-line no-bitwise
return (hash >>> 0).toString();
}
/**
* Creates a progress callback that scales the current file's progress
* relative to the total size of all files being downloaded.
* @param totalLength - The total size of all files in the download batch.
* @param previousFilesTotalLength - The sum of sizes of files already downloaded.
* @param currentFileLength - The size of the file currently being downloaded.
* @param setProgress - The main callback to update the global progress.
* @returns A function that accepts the progress (0-1) of the current file.
*/
export function calculateDownloadProgress(
totalLength: number,
previousFilesTotalLength: number,
currentFileLength: number,
setProgress: (downloadProgress: number) => void
) {
return (progress: number) => {
if (
progress === 1 &&
previousFilesTotalLength === totalLength - currentFileLength
) {
setProgress(1);
return;
}
// Avoid division by zero
if (totalLength === 0) {
setProgress(0);
return;
}
const baseProgress = previousFilesTotalLength / totalLength;
const scaledProgress = progress * (currentFileLength / totalLength);
const updatedProgress = baseProgress + scaledProgress;
setProgress(updatedProgress);
};
}
/**
* 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
* @param uri - The URI of the file being downloaded.
*/
export async function 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' });
}
}
function getCountryCode(): string {
try {
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
const regionTag = locale.split('-').pop();
if (regionTag && regionTag.length === 2) {
return regionTag.toUpperCase();
}
} catch {}
return 'UNKNOWN';
}
export function isEmulator(): boolean {
return global.__rne_isEmulator;
}
function getModelNameFromUri(uri: string): string {
const knownName = getModelNameForUrl(uri);
if (knownName) {
return knownName;
}
const pathname = new URL(uri).pathname;
const filename = pathname.split('/').pop() ?? uri;
return filename.replace(/\.[^.]+$/, '');
}
/**
* Sends a download event to the analytics endpoint.
* @param uri - The URI of the downloaded resource.
*/
export function triggerDownloadEvent(uri: string) {
try {
fetch(DOWNLOAD_EVENT_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
modelName: getModelNameFromUri(uri),
countryCode: getCountryCode(),
isEmulator: isEmulator(),
libVersion: LIB_VERSION,
}),
});
} catch (e) {}
}
/**
* Generates a safe filename from a URI by removing the protocol and replacing special characters.
* @param uri - The source URI.
* @returns A sanitized filename string.
*/
export function getFilenameFromUri(uri: string) {
let cleanUri = uri.replace(/^https?:\/\//, '');
cleanUri = cleanUri.split('#')?.[0] ?? cleanUri;
return cleanUri.replace(/[^a-zA-Z0-9._-]/g, '_');
}
}