From 4092854b0bea9e1fc02f29222c6f7cd876e565a5 Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Mon, 17 Aug 2026 09:43:20 +0800
Subject: [PATCH] feat:项目基础整理

---
 apps/workbench-console/src/api/artifacts.ts |  133 +++++++++++++-------------------------------
 1 files changed, 39 insertions(+), 94 deletions(-)

diff --git a/apps/workbench-console/src/api/artifacts.ts b/apps/workbench-console/src/api/artifacts.ts
index 47de970..21202bc 100644
--- a/apps/workbench-console/src/api/artifacts.ts
+++ b/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); }); }

--
Gitblit v1.9.3