import { Fetcher, apiRequest } from "./apiClient"; import { ImageFeatures } from "./checkpointsApi"; export interface EpisodeSummary { episode_index: number; length: number; duration_s: number | null; tasks: string[]; } export interface EpisodeListResponse { success: boolean; message?: string; repo_id: string; fps: number | null; robot_type: string | null; total_episodes: number; total_frames: number | null; image_features: ImageFeatures; episodes: EpisodeSummary[]; } export interface EpisodeCamera { name: string; /** * Where this episode starts inside the camera's mp4. Several episodes share * one file, so this is almost never 0 — and each camera carries its own * offset, which is why syncing secondaries means mapping through this value * rather than copying the driver's currentTime. */ from_timestamp: number | null; to_timestamp: number | null; chunk: number; file_index: number; } export interface EpisodeDetail { success: boolean; message?: string; repo_id: string; episode_index: number; length: number; duration_s: number | null; tasks: string[]; fps: number | null; robot_type: string | null; cameras: EpisodeCamera[]; prev_episode: number | null; next_episode: number | null; total_episodes: number; } export interface MotionResponse { success: boolean; message?: string; episode_index: number; /** One aggregate value per frame: the sum of absolute joint deltas. */ motion: number[]; max_motion: number; } export interface Thumbnail { frame_index: number; data_uri: string; } export interface ThumbnailsResponse { success: boolean; message?: string; episode_index: number; camera: string; thumbnails: Thumbnail[]; } export async function listEpisodes( baseUrl: string, fetcher: Fetcher, repoId: string, signal?: AbortSignal, ): Promise { const qs = new URLSearchParams({ repo_id: repoId }); return apiRequest(baseUrl, fetcher, `/dataset-episodes?${qs}`, { signal, action: "List episodes", }); } export async function getEpisode( baseUrl: string, fetcher: Fetcher, repoId: string, episodeIndex: number, signal?: AbortSignal, ): Promise { const qs = new URLSearchParams({ repo_id: repoId, episode_index: String(episodeIndex), }); return apiRequest(baseUrl, fetcher, `/dataset-episode?${qs}`, { signal, action: "Load episode", }); } export async function getMotion( baseUrl: string, fetcher: Fetcher, repoId: string, episodeIndex: number, signal?: AbortSignal, ): Promise { const qs = new URLSearchParams({ repo_id: repoId, episode_index: String(episodeIndex), }); return apiRequest(baseUrl, fetcher, `/dataset-motion?${qs}`, { signal, action: "Load motion trace", }); } export async function getThumbnails( baseUrl: string, fetcher: Fetcher, repoId: string, episodeIndex: number, camera: string, count: number, signal?: AbortSignal, ): Promise { const qs = new URLSearchParams({ repo_id: repoId, episode_index: String(episodeIndex), camera, count: String(count), }); return apiRequest(baseUrl, fetcher, `/dataset-thumbnails?${qs}`, { signal, action: "Load thumbnails", }); } /** * URL for one camera's mp4. Goes straight into