UNPKG

@huggingface/transformers

Version:

State-of-the-art Machine Learning for the web. Run 🤗 Transformers directly in your browser, with no need for a server!

169 lines (145 loc) • 6.09 kB
import { ERROR_MAPPING, REPO_ID_REGEX } from './constants.js'; import { logger } from '../logger.js'; /** * Joins multiple parts of a path into a single path, while handling leading and trailing slashes. * * @param {...string} parts Multiple parts of a path. * @returns {string} A string representing the joined path. */ export function pathJoin(...parts) { // https://stackoverflow.com/a/55142565 parts = parts.map((part, index) => { if (index) { part = part.replace(new RegExp('^/'), ''); } if (index !== parts.length - 1) { part = part.replace(new RegExp('/$'), ''); } return part; }); return parts.join('/'); } /** * Determines whether the given string is a valid URL. * @param {string|URL} string The string to test for validity as an URL. * @param {string[]} [protocols=null] A list of valid protocols. If specified, the protocol must be in this list. * @param {string[]} [validHosts=null] A list of valid hostnames. If specified, the URL's hostname must be in this list. * @returns {boolean} True if the string is a valid URL, false otherwise. */ export function isValidUrl(string, protocols = null, validHosts = null) { let url; try { url = new URL(string); } catch (_) { return false; } if (protocols && !protocols.includes(url.protocol)) { return false; } if (validHosts && !validHosts.includes(url.hostname)) { return false; } return true; } /** * Tests whether a string is a valid Hugging Face model ID or not. * Adapted from https://github.com/huggingface/huggingface_hub/blob/6378820ebb03f071988a96c7f3268f5bdf8f9449/src/huggingface_hub/utils/_validators.py#L119-L170 * * @param {string} string The string to test * @returns {boolean} True if the string is a valid model ID, false otherwise. */ export function isValidHfModelId(string) { if (!REPO_ID_REGEX.test(string)) return false; if (string.includes('..') || string.includes('--')) return false; if (string.endsWith('.git') || string.endsWith('.ipynb')) return false; return true; } /** * Helper method to handle fatal errors that occur while trying to load a file from the Hugging Face Hub. * @param {number} status The HTTP status code of the error. * @param {string} remoteURL The URL of the file that could not be loaded. * @param {boolean} fatal Whether to raise an error if the file could not be loaded. * @returns {null} Returns `null` if `fatal = true`. * @throws {Error} If `fatal = false`. */ export function handleError(status, remoteURL, fatal) { if (!fatal) { // File was not loaded correctly, but it is optional. // TODO in future, cache the response? return null; } const message = ERROR_MAPPING[status] ?? `Error (${status}) occurred while trying to load file`; throw Error(`${message}: "${remoteURL}".`); } /** * Read and track progress when reading a Response object * * @param {Response|import('./FileResponse.js').FileResponse} response The Response object to read * @param {(data: {progress: number, loaded: number, total: number}) => void} progress_callback The function to call with progress updates * @param {number} [expectedSize] The expected size of the file (used when content-length header is missing) * @returns {Promise<Uint8Array>} A Promise that resolves with the Uint8Array buffer */ export async function readResponse(response, progress_callback, expectedSize) { const contentLength = response.headers.get('Content-Length'); // Use content-length if available, otherwise fall back to expectedSize (from metadata) let total = contentLength ? parseInt(contentLength, 10) : (expectedSize ?? 0); if (contentLength === null && !expectedSize) { logger.warn('Unable to determine content-length from response headers. Will expand buffer when needed.'); } let buffer = new Uint8Array(total); let loaded = 0; const reader = response.body.getReader(); async function read() { const { done, value } = await reader.read(); if (done) return; const newLoaded = loaded + value.length; if (newLoaded > total) { total = newLoaded; // Adding the new data will overflow buffer. // In this case, we extend the buffer const newBuffer = new Uint8Array(total); // copy contents newBuffer.set(buffer); buffer = newBuffer; } buffer.set(value, loaded); loaded = newLoaded; const progress = (loaded / total) * 100; // Call your function here progress_callback({ progress, loaded, total }); return read(); } // Actually read await read(); return buffer; } /** * Checks if the given URL is a blob URL (created via URL.createObjectURL). * Blob URLs should not be cached as they are temporary in-memory references. * @param {string} url - The URL to check. * @returns {boolean} True if the URL is a blob URL, false otherwise. */ export function isBlobURL(url) { return isValidUrl(url, ['blob:']); } /** * Converts any URL to an absolute URL if needed. * If the URL is already absolute (http://, https://, or blob:), returns it unchanged (handled by new URL(...)). * Otherwise, resolves it relative to the current page location (browser) or module location (Node/Bun/Deno). * @param {string} url - The URL to convert (can be relative or absolute). * @returns {string} The absolute URL. */ export function toAbsoluteURL(url) { let baseURL; if (typeof location !== 'undefined' && location.href) { // Browser environment: use location.href baseURL = location.href; } else if (typeof import.meta !== 'undefined' && import.meta.url) { // Node.js/Bun/Deno module environment: use import.meta.url baseURL = import.meta.url; } else { // Fallback: if no base is available, return the URL unchanged return url; } return new URL(url, baseURL).href; }