Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| /** | |
| * Client for the FastAPI annotation backend in `backend/`. | |
| * | |
| * The backend URL is configured via the `NEXT_PUBLIC_ANNOTATE_BACKEND_URL` | |
| * env var so it can be statically substituted by Next.js. When unset, all | |
| * annotation write paths are disabled and the UI falls back to sessionStorage | |
| * for read/edit only. | |
| */ | |
| import type { LanguageAtom } from "../types/language.types"; | |
| const ENV_URL = (() => { | |
| const v = | |
| typeof process !== "undefined" | |
| ? process.env.NEXT_PUBLIC_ANNOTATE_BACKEND_URL | |
| : undefined; | |
| return (v || "").trim() || null; | |
| })(); | |
| export function isAnnotateBackendEnabled(): boolean { | |
| return !!ENV_URL; | |
| } | |
| export function getAnnotateBackendUrl(): string | null { | |
| return ENV_URL; | |
| } | |
| interface DatasetIdent { | |
| repoId?: string | null; | |
| localPath?: string | null; | |
| revision?: string | null; | |
| } | |
| function buildUrl(path: string, ident: DatasetIdent): string { | |
| if (!ENV_URL) throw new Error("Annotate backend not configured"); | |
| const url = new URL(path, ENV_URL); | |
| if (ident.repoId) url.searchParams.set("repo_id", ident.repoId); | |
| if (ident.revision) url.searchParams.set("revision", ident.revision); | |
| if (ident.localPath) url.searchParams.set("local_path", ident.localPath); | |
| return url.toString(); | |
| } | |
| export async function pingBackend(): Promise<boolean> { | |
| if (!ENV_URL) return false; | |
| try { | |
| const res = await fetch(new URL("/api/health", ENV_URL).toString()); | |
| return res.ok; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| export async function loadDataset( | |
| ident: DatasetIdent, | |
| ): Promise<{ ok: boolean }> { | |
| if (!ENV_URL) return { ok: false }; | |
| const res = await fetch(new URL("/api/dataset/load", ENV_URL).toString(), { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| repo_id: ident.repoId || null, | |
| revision: ident.revision || null, | |
| local_path: ident.localPath || null, | |
| }), | |
| }); | |
| return { ok: res.ok }; | |
| } | |
| export async function fetchEpisodeAtoms( | |
| episodeId: number, | |
| ident: DatasetIdent, | |
| ): Promise<LanguageAtom[]> { | |
| if (!ENV_URL) return []; | |
| await loadDataset(ident); | |
| const res = await fetch(buildUrl(`/api/episodes/${episodeId}/atoms`, ident)); | |
| if (!res.ok) { | |
| throw new Error(`fetch atoms: ${res.status}`); | |
| } | |
| const data = (await res.json()) as { atoms?: LanguageAtom[] }; | |
| return data.atoms || []; | |
| } | |
| export async function saveEpisodeAtoms( | |
| episodeId: number, | |
| ident: DatasetIdent, | |
| atoms: LanguageAtom[], | |
| ): Promise<{ path: string | null }> { | |
| if (!ENV_URL) return { path: null }; | |
| const res = await fetch( | |
| new URL(`/api/episodes/${episodeId}/atoms`, ENV_URL).toString(), | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| episode_index: episodeId, | |
| repo_id: ident.repoId || null, | |
| local_path: ident.localPath || null, | |
| atoms, | |
| }), | |
| }, | |
| ); | |
| if (!res.ok) { | |
| const text = await res.text().catch(() => `${res.status}`); | |
| throw new Error(text || `save atoms: ${res.status}`); | |
| } | |
| const data = (await res.json().catch(() => ({}))) as { path?: string | null }; | |
| return { path: data.path ?? null }; | |
| } | |
| export async function fetchFrameTimestamps( | |
| episodeId: number, | |
| ident: DatasetIdent, | |
| ): Promise<number[]> { | |
| if (!ENV_URL) return []; | |
| const res = await fetch( | |
| buildUrl(`/api/episodes/${episodeId}/frame_timestamps`, ident), | |
| ); | |
| if (!res.ok) return []; | |
| const data = (await res.json()) as { timestamps?: number[] }; | |
| return data.timestamps || []; | |
| } | |
| export async function exportDataset( | |
| ident: DatasetIdent, | |
| outputDir?: string | null, | |
| copyVideos = false, | |
| ): Promise<{ | |
| output_dir: string; | |
| persistent_rows: number; | |
| event_rows: number; | |
| }> { | |
| if (!ENV_URL) throw new Error("Annotate backend not configured"); | |
| const res = await fetch(new URL("/api/export", ENV_URL).toString(), { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| repo_id: ident.repoId || null, | |
| revision: ident.revision || null, | |
| local_path: ident.localPath || null, | |
| output_dir: outputDir || null, | |
| copy_videos: !!copyVideos, | |
| }), | |
| }); | |
| if (!res.ok) { | |
| const text = await res.text().catch(() => `${res.status}`); | |
| throw new Error(text || `export: ${res.status}`); | |
| } | |
| return res.json(); | |
| } | |
| export interface PushToHubResult { | |
| ok: boolean; | |
| repo_id: string; | |
| url: string; | |
| message: string; | |
| } | |
| export async function pushToHub( | |
| ident: DatasetIdent, | |
| hfToken: string, | |
| pushInPlace: boolean, | |
| newRepoId: string | null, | |
| privateRepo: boolean, | |
| commitMessage: string, | |
| ): Promise<PushToHubResult> { | |
| if (!ENV_URL) throw new Error("Annotate backend not configured"); | |
| const res = await fetch(new URL("/api/push_to_hub", ENV_URL).toString(), { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| repo_id: ident.repoId || null, | |
| revision: ident.revision || null, | |
| local_path: ident.localPath || null, | |
| hf_token: hfToken, | |
| push_in_place: pushInPlace, | |
| new_repo_id: newRepoId || null, | |
| private: privateRepo, | |
| commit_message: commitMessage, | |
| }), | |
| }); | |
| if (!res.ok) { | |
| const text = await res.text().catch(() => `${res.status}`); | |
| throw new Error(text || `push: ${res.status}`); | |
| } | |
| return res.json(); | |
| } | |