/** * Local LeRobot dataset roots (absolute filesystem paths) are encoded in URLs as * `/local//episode_0`. File bytes are served via * `/api/local-dataset/file?root=...&rel=...`. */ export const LOCAL_ORG = "local"; export const LOCAL_REPO_PREFIX = `${LOCAL_ORG}/`; function utf8ToBase64Url(s: string): string { const bytes = new TextEncoder().encode(s); let bin = ""; for (let i = 0; i < bytes.length; i++) { bin += String.fromCharCode(bytes[i]!); } const b64 = typeof btoa !== "undefined" ? btoa(bin) : Buffer.from(bytes).toString("base64"); return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } function base64UrlToUtf8(s: string): string { const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - (s.length % 4)); const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + pad; let bin: string; if (typeof atob !== "undefined") { bin = atob(b64); } else { bin = Buffer.from(b64, "base64").toString("binary"); } const bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) { bytes[i] = bin.charCodeAt(i); } return new TextDecoder().decode(bytes); } export function normalizeUserLocalPathInput(input: string): string { const t = input.trim(); if (t.startsWith("file://")) { try { const u = new URL(t); return decodeURIComponent(u.pathname); } catch { return t; } } return t; } export function looksLikeAbsoluteFilesystemPath(input: string): boolean { const t = normalizeUserLocalPathInput(input); if (!t) return false; if (t.startsWith("/")) return true; return /^[A-Za-z]:[\\/]/.test(t); } export function encodeLocalDatasetRoot(absolutePath: string): string { return utf8ToBase64Url(absolutePath); } export function decodeLocalDatasetSegment( encodedSegment: string, ): string | null { if (!encodedSegment) return null; try { return base64UrlToUtf8(encodedSegment); } catch { return null; } } export function localRepoIdFromAbsolutePath(absolutePath: string): string { return `${LOCAL_REPO_PREFIX}${encodeLocalDatasetRoot(absolutePath)}`; } export function isLocalRepoId(repoId: string): boolean { return ( repoId.startsWith(LOCAL_REPO_PREFIX) && repoId.length > LOCAL_REPO_PREFIX.length ); } export function localDatasetFileApiPath( rootEncoded: string, relPath: string, ): string { const q = new URLSearchParams({ root: rootEncoded, rel: relPath, }); return `/api/local-dataset/file?${q.toString()}`; } /** Parse our local file API URL (absolute or relative) for server-side fs shortcut. */ export function tryParseLocalDatasetFileApiUrl( url: string, ): { rootEncoded: string; relPath: string } | null { const marker = "/api/local-dataset/file"; const idx = url.indexOf(marker); if (idx === -1) return null; const after = url.slice(idx + marker.length); const queryStart = after.indexOf("?"); if (queryStart === -1) return null; const search = after.slice(queryStart + 1); try { const params = new URLSearchParams(search); const root = params.get("root"); const rel = params.get("rel"); if (!root || !rel) return null; return { rootEncoded: root, relPath: rel }; } catch { return null; } }