shuishen
2 days ago 4092854b0bea9e1fc02f29222c6f7cd876e565a5
apps/workbench-console/src/api/artifacts.ts
@@ -1,101 +1,46 @@
export interface Detection {
  class_id: number;
  class_name: string;
  confidence: number;
  bbox_xyxy: number[];
}
export interface DetectionImage {
  file: string;
  annotated_file: string;
  width: number;
  height: number;
  detections: Detection[];
}
export interface DetectionRun {
  detection_count: number;
  processed_images: number;
  elapsed_seconds: number;
  device: string;
  model: string;
  confidence: number;
  input_dir: string;
  notes: string[];
}
export interface TrajectoryEvent {
  event_id: string;
  event_type: string;
  track_ids: string[];
  start_time: string;
  end_time: string;
  duration_seconds: number;
}
export interface TrajectoryRun {
  case_count: number;
  track_count: number;
  event_count: number;
  dropped_duplicate_observations: number;
  elapsed_seconds: number;
  device: string;
  input: string;
  thresholds_by_case: Record<string, Record<string, number>>;
}
export interface TrajectorySummary {
  track_id: string;
  entity_type: string;
  point_count: string;
  distance_m: string;
  average_speed_mps: string;
  behavior_labels: string;
}
export interface Detection { class_id: number; class_name: string; confidence: number; bbox_xyxy: number[]; }
export interface DetectionImage { file: string; annotated_file: string; width: number; height: number; detections: Detection[]; }
export interface DetectionRun { detection_count: number; processed_images: number; elapsed_seconds: number; device: string; model: string; confidence: number; input_dir: string; notes: string[]; created_at?: string; }
export interface DetectionCase { id: string; label: string; note: string; artifactRoot: string; inputRoot: string; createdAt: string; run: DetectionRun; images: DetectionImage[]; }
export interface TrajectoryEvent { event_id: string; event_type: string; track_ids: string[]; start_time: string; end_time: string; duration_seconds: number; }
export interface TrajectoryCaseRun { case_id: string; input_count: number; track_count: number; event_count: number; dropped_duplicate_observations: number; elapsed_seconds: number; device: string; input: string; thresholds: Record<string, number>; created_at?: string; }
export interface TrajectorySummary { track_id: string; entity_type: string; point_count: string; distance_m: string; average_speed_mps: string; behavior_labels: string; }
export interface TrajectoryCase { id: string; label: string; note: string; artifactRoot: string; showSpatialContext: boolean; showFlyableZones: boolean; createdAt: string; run: TrajectoryCaseRun; events: TrajectoryEvent[]; summary: TrajectorySummary[]; }
interface CaseDefinition { id: string; label: string; note: string; artifactRoot: string; createdAt: string; }
interface TrajectoryDefinition extends CaseDefinition { showSpatialContext: boolean; showFlyableZones: boolean; }
interface DetectionDefinition extends CaseDefinition { inputRoot: string; }
export interface UploadFilePayload { name: string; content: string; }
export const artifactUrl = (path: string) => `/${path.replace(/\\/g, "/").split("/").map(encodeURIComponent).join("/")}`;
async function getJson<T>(path: string): Promise<T> {
  const response = await fetch(artifactUrl(path), { cache: "no-store" });
  if (!response.ok) throw new Error(`无法读取 ${path} (${response.status})`);
  return response.json() as Promise<T>;
async function responseJson<T>(response: Response): Promise<T> {
  const payload = await response.json().catch(() => ({})) as { error?: string } & T;
  if (!response.ok) throw new Error(payload.error || `本地服务请求失败 (${response.status})`);
  return payload;
}
async function getJson<T>(path: string): Promise<T> { return responseJson<T>(await fetch(artifactUrl(path), { cache: "no-store" })); }
async function getText(path: string): Promise<string> { const response = await fetch(artifactUrl(path), { cache: "no-store" }); if (!response.ok) throw new Error(`无法读取 ${path} (${response.status})`); return response.text(); }
function parseCsv(text: string): TrajectorySummary[] { const lines = text.trim().split(/\r?\n/); const headers = lines.shift()?.replace(/^\uFEFF/, "").split(",") ?? []; return lines.filter(Boolean).map((line) => { const values = line.split(","); return Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ""])) as unknown as TrajectorySummary; }); }
export async function loadTrajectoryArtifacts(): Promise<Record<string, TrajectoryCase>> {
  const { runs } = await getJson<{ runs: TrajectoryDefinition[] }>("api/trajectory/runs");
  const cases = await Promise.all(runs.map(async (definition) => {
    const [run, eventPayload, summary] = await Promise.all([getJson<TrajectoryCaseRun>(`${definition.artifactRoot}/run_metadata.json`), getJson<{ events: TrajectoryEvent[] }>(`${definition.artifactRoot}/events.json`), getText(`${definition.artifactRoot}/trajectory_summary.csv`)]);
    return { ...definition, run, events: eventPayload.events, summary: parseCsv(summary) } satisfies TrajectoryCase;
  }));
  return Object.fromEntries(cases.map((item) => [item.id, item]));
}
async function getText(path: string): Promise<string> {
  const response = await fetch(artifactUrl(path), { cache: "no-store" });
  if (!response.ok) throw new Error(`无法读取 ${path} (${response.status})`);
  return response.text();
export async function loadDetectionArtifacts(): Promise<Record<string, DetectionCase>> {
  const { runs } = await getJson<{ runs: DetectionDefinition[] }>("api/object-detection/runs");
  const cases = await Promise.all(runs.map(async (definition) => {
    const [run, payload] = await Promise.all([getJson<DetectionRun>(`${definition.artifactRoot}/run_metadata.json`), getJson<{ images: DetectionImage[] }>(`${definition.artifactRoot}/detections.json`)]);
    return { ...definition, run, images: payload.images } satisfies DetectionCase;
  }));
  return Object.fromEntries(cases.map((item) => [item.id, item]));
}
function parseCsv(text: string): TrajectorySummary[] {
  const lines = text.trim().split(/\r?\n/);
  const headers = lines.shift()?.replace(/^\uFEFF/, "").split(",") ?? [];
  return lines.filter(Boolean).map((line) => {
    const values = line.split(",");
    return Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ""])) as unknown as TrajectorySummary;
  });
}
export async function loadDetectionArtifacts() {
  const [run, payload] = await Promise.all([
    getJson<DetectionRun>("shared/outputs/01-object-detection/run_metadata.json"),
    getJson<{ images: DetectionImage[] }>("shared/outputs/01-object-detection/detections.json")
  ]);
  return { run, images: payload.images };
}
export async function loadTrajectoryArtifacts() {
  const [run, difficultEvents, difficultSummary, normalSummary] = await Promise.all([
    getJson<TrajectoryRun>("shared/outputs/15-trajectory-analysis/run_metadata.json"),
    getJson<{ events: TrajectoryEvent[] }>("shared/outputs/15-trajectory-analysis/difficult/events.json"),
    getText("shared/outputs/15-trajectory-analysis/difficult/trajectory_summary.csv"),
    getText("shared/outputs/15-trajectory-analysis/normal/trajectory_summary.csv")
  ]);
  return {
    run,
    cases: {
      difficult: { events: difficultEvents.events, summary: parseCsv(difficultSummary) },
      normal: { events: [], summary: parseCsv(normalSummary) }
    }
  };
}
async function postRun<T>(path: string, body: object): Promise<T> { return responseJson<T>(await fetch(artifactUrl(path), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) })); }
export async function createTrajectoryRun(files: { flight: UploadFilePayload; route: UploadFilePayload; restricted: UploadFilePayload; flyable?: UploadFilePayload }) { return postRun<{ run: TrajectoryDefinition }>("api/trajectory/runs", { files }); }
export async function createDetectionRun(images: UploadFilePayload[]) { return postRun<{ run: DetectionDefinition }>("api/object-detection/runs", { images }); }
export function readFileAsPayload(file: File): Promise<UploadFilePayload> { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = () => reject(new Error(`无法读取 ${file.name}`)); reader.onload = () => { const value = String(reader.result ?? ""); resolve({ name: file.name, content: value.slice(value.indexOf(",") + 1) }); }; reader.readAsDataURL(file); }); }